From e8c5fb387831fe9ff8bd41462176805a9c382999 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 27 Sep 2024 08:30:28 -0700 Subject: [PATCH] Automated rollback of commit 8ef7b705bb33de6ee9025c01bdf65a23f73926e3. *** Reason for rollback *** The experiment with using sqlite didn't pan out. We might find other uses for it in the future, but we can always resurrect it from version control; no need to saddle Bazel with the additional dependency until then. *** Original change description *** Implement a JNI wrapper around SQLite. This will be used by the implementation of garbage collection for the disk cache, as discussed in https://github.com/bazelbuild/bazel/issues/5139 and the linked design doc. I judge this to be preferred over https://github.com/xerial/sqlite-jdbc for the following reasons: 1. It's a much smaller dependency. 2. The JDBC API is too generic and becomes awkward to use when dealing with the peculiarities of SQLite. 3. We can (more easily) compile it from source for all host platforms, including the BSDs. *** PiperOrigin-RevId: 679600756 Change-Id: Ic3748fa30404a31504426c523c9b9a60ec451863 --- MODULE.bazel | 1 - MODULE.bazel.lock | 2 - repositories.bzl | 1 - .../devtools/build/lib/remote/disk/BUILD | 1 - .../build/lib/remote/disk/Sqlite.java | 485 ------------------ src/main/native/BUILD | 18 - src/main/native/sqlite_jni.cc | 242 --------- src/main/native/windows/BUILD | 1 - .../build/lib/remote/disk/SqliteTest.java | 309 ----------- 9 files changed, 1060 deletions(-) delete mode 100644 src/main/java/com/google/devtools/build/lib/remote/disk/Sqlite.java delete mode 100644 src/main/native/sqlite_jni.cc delete mode 100644 src/test/java/com/google/devtools/build/lib/remote/disk/SqliteTest.java diff --git a/MODULE.bazel b/MODULE.bazel index 65ed4c62d901b1..afe96587a7c142 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -21,7 +21,6 @@ bazel_dep(name = "rules_pkg", version = "0.9.1") bazel_dep(name = "stardoc", version = "0.7.1", repo_name = "io_bazel_skydoc") bazel_dep(name = "zstd-jni", version = "1.5.2-3.bcr.1") bazel_dep(name = "blake3", version = "1.5.1.bcr.1") -bazel_dep(name = "sqlite3", version = "3.42.0.bcr.1") bazel_dep(name = "zlib", version = "1.3.1.bcr.3") bazel_dep(name = "rules_cc", version = "0.0.10") bazel_dep(name = "rules_java", version = "7.11.1") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 4deb6d62c237c3..9c7196ee1a3da0 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -115,8 +115,6 @@ "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_testing/0.6.0/MODULE.bazel": "8518d53bc742c462536d3f1a0de0c265bd7b51f32797fea4132007223ed2926f", "https://bcr.bazel.build/modules/rules_testing/0.6.0/source.json": "915ae13ae2247c986cc57289f21e7f1d9711cd2ecfdf5867b51dc0484f3b043b", - "https://bcr.bazel.build/modules/sqlite3/3.42.0.bcr.1/MODULE.bazel": "e58096b04bc268d13f6b9047c385b7d38ac62f4d1ebd1ed3d0a852378c10b05a", - "https://bcr.bazel.build/modules/sqlite3/3.42.0.bcr.1/source.json": "9abf49bde81eca775c4193d0a5fd2b08bb577705fdb474f9a8a28643b7d6216d", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", diff --git a/repositories.bzl b/repositories.bzl index c3fb4d69db270b..80d0f193c7613b 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -44,7 +44,6 @@ DIST_ARCHIVE_REPOS = [get_canonical_repo_name(repo) for repo in [ "rules_pkg", "rules_proto", "rules_python", - "sqlite3", "upb", "zlib", "zstd-jni", diff --git a/src/main/java/com/google/devtools/build/lib/remote/disk/BUILD b/src/main/java/com/google/devtools/build/lib/remote/disk/BUILD index 6e0736de4aaba8..b807afe7e32e9d 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/disk/BUILD +++ b/src/main/java/com/google/devtools/build/lib/remote/disk/BUILD @@ -16,7 +16,6 @@ java_library( srcs = glob(["*.java"]), deps = [ "//src/main/java/com/google/devtools/build/lib/concurrent", - "//src/main/java/com/google/devtools/build/lib/jni", "//src/main/java/com/google/devtools/build/lib/remote:store", "//src/main/java/com/google/devtools/build/lib/remote/common", "//src/main/java/com/google/devtools/build/lib/remote/common:cache_not_found_exception", diff --git a/src/main/java/com/google/devtools/build/lib/remote/disk/Sqlite.java b/src/main/java/com/google/devtools/build/lib/remote/disk/Sqlite.java deleted file mode 100644 index d74134887c4ddc..00000000000000 --- a/src/main/java/com/google/devtools/build/lib/remote/disk/Sqlite.java +++ /dev/null @@ -1,485 +0,0 @@ -// Copyright 2024 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package com.google.devtools.build.lib.remote.disk; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.collect.ImmutableList; -import com.google.devtools.build.lib.jni.JniLoader; -import com.google.devtools.build.lib.vfs.Path; -import java.io.IOException; -import java.util.HashSet; -import javax.annotation.Nullable; - -/** - * A wrapper around the SQLite C API. - * - *

Exposes only the subset of the SQLite C API required by Bazel. - */ -public final class Sqlite { - - private Sqlite() {} - - public static final int ERR_ERROR = 1; - public static final int ERR_INTERNAL = 2; - public static final int ERR_PERM = 3; - public static final int ERR_ABORT = 4; - public static final int ERR_BUSY = 5; - public static final int ERR_LOCKED = 6; - public static final int ERR_NOMEM = 7; - public static final int ERR_READONLY = 8; - public static final int ERR_INTERRUPT = 9; - public static final int ERR_IOERR = 10; - public static final int ERR_CORRUPT = 11; - public static final int ERR_NOTFOUND = 12; - public static final int ERR_FULL = 13; - public static final int ERR_CANTOPEN = 14; - public static final int ERR_PROTOCOL = 15; - public static final int ERR_EMPTY = 16; - public static final int ERR_SCHEMA = 17; - public static final int ERR_TOOBIG = 18; - public static final int ERR_CONSTRAINT = 19; - public static final int ERR_MISMATCH = 20; - public static final int ERR_MISUSE = 21; - public static final int ERR_NOLFS = 22; - public static final int ERR_AUTH = 23; - public static final int ERR_FORMAT = 24; - public static final int ERR_RANGE = 25; - public static final int ERR_NOTADB = 26; - public static final int ERR_NOTICE = 27; - public static final int ERR_WARNING = 28; - - static { - JniLoader.loadJni(); - } - - /** An exception thrown when the SQLite C API returns an error. */ - public static final class SqliteException extends IOException { - private final int code; - - public SqliteException(int code) { - super(String.format("SQLite error: %s (%d)", errStr(code), code)); - this.code = code; - } - - /** - * Returns the SQLite error code. - * - *

Error codes SQLITE_OK, SQLITE_ROW and SQLITE_DONE never cause an exception to be thrown. - */ - public int getCode() { - return code; - } - } - - /** - * Opens a connection to a database, creating it in an empty state if it doesn't yet exist. - * - * @param path the path to the database - * @throws IOException if an error occurred while opening the connection - */ - public static Connection newConnection(Path path) throws IOException { - checkNotNull(path); - return new Connection(path); - } - - /** - * A connection to a database. - * - *

This class is *not* thread-safe. A single connection should be used by a single thread. Use - * separate connections to the same database for multithreaded access. - */ - public static final class Connection implements AutoCloseable { - - // C pointer to the `sqlite3` handle. Zero means the connection has been closed. - private long connPtr; - - // Tracks open statements so they can also be closed when the connection is closed. - private final HashSet openStatements = new HashSet<>(); - - private Connection(Path path) throws IOException { - this.connPtr = openConn(path.getPathString()); - } - - /** - * Closes the connection, rendering it unusable. - * - *

As a convenience, every {@link Statement} associated with the connection is also closed, - * with any errors silently ignored. - * - *

Multiple calls have no further effect. - * - * @throws IOException if an error occurred while closing the connection - */ - @Override - public void close() throws IOException { - if (connPtr != 0) { - // SQLite won't let us close the connection before first closing associated statements. - for (Statement stmt : ImmutableList.copyOf(openStatements)) { - stmt.close(); - } - closeConn(connPtr); - connPtr = 0; - } - } - - /** - * Creates a new {@link Statement}. - * - * @param sql a string containing a single SQL statement - * @throws IOException if the string contains multiple SQL statements, or the single SQL - * statement could not be parsed and validated - */ - public Statement newStatement(String sql) throws IOException { - checkState(connPtr != 0, "newStatement() called in invalid state"); - Statement stmt = new Statement(this, sql); - openStatements.add(stmt); - return stmt; - } - - /** - * Executes a statement to completion and discards its result. - * - *

For statements whose result is of interest, or statements that will be executed multiple - * times, use {@link #newStatement} and {@link Statement#executeQuery}. - * - * @throws IOException if the string contains multiple SQL statements; or the single SQL - * statement could not be parsed and validated; or an execution error occurred - */ - public void executeUpdate(String sql) throws IOException { - try (Statement stmt = new Statement(this, sql)) { - stmt.executeUpdate(); - } - } - } - - /** - * A prepared statement. - * - *

Provides a facility to bind values to parameters and execute the statement by calling one of - * {@link #executeQuery} or {@link #executeUpdate}. The same statement may be executed multiple - * times, with its parameters possibly bound to different values, but there can be at most one - * ongoing execution at a time. - * - *

Parameters that haven't been bound or whose binding has been cleared behave as null. - */ - public static final class Statement implements AutoCloseable { - - // The connection owning this statement. - private Connection conn; - - // C pointer to te `sqlite3_stmt` handle. Zero means the statement has been closed. - private long stmtPtr; - - // The result of current execution, or null if no execution is ongoing. - @Nullable private Result currentResult; - - private Statement(Connection conn, String sql) throws IOException { - this.conn = conn; - stmtPtr = prepareStmt(conn.connPtr, sql); - if (stmtPtr == -1) { - // Special value returned when a multi-statement string is detected. - throw new IOException("unsupported multi-statement string"); - } - } - - /** - * Closes the statement, rendering it unusable. - * - *

A {@link Result} previously returned by {@link #executeQuery} is also closed, with any - * error silently ignored. - * - *

Multiple calls have no additional effect. - */ - @Override - public void close() { - if (stmtPtr != 0) { - if (currentResult != null) { - try { - currentResult.close(); - } catch (IOException e) { - // Intentionally ignored: an error always pertains to a particular execution, and should - // only be reported when Result#close is called directly. - } - } - try { - finalizeStmt(stmtPtr); - } catch (IOException e) { - // Cannot occur since the statement has been reset by Result#close. - throw new IllegalStateException("unexpected exception thrown by finalize", e); - } - conn.openStatements.remove(this); - conn = null; - stmtPtr = 0; - } - } - - /** - * Binds a long value to the statement's i-th parameter, counting from 1. - * - *

The binding remains in effect until it is cleared or another value is bound to the same - * parameter. - * - *

Must not be called after {@link #executeQuery} returns a {@link Result} and before the - * {@link Result} is closed. - */ - public void bindLong(int i, long val) throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "bindLong() called in invalid state"); - bindStmtLong(stmtPtr, i, val); - } - - /** - * Binds a double value to the statement's i-th parameter, counting from 1. - * - *

The binding remains in effect until it is cleared or another value is bound to the same - * parameter. - * - *

Must not be called after {@link #executeQuery} returns a {@link Result} and before the - * {@link Result} is closed. - */ - public void bindDouble(int i, double val) throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "bindDouble() called in invalid state"); - bindStmtDouble(stmtPtr, i, val); - } - - /** - * Binds a non-null string value to the statement's i-th parameter, counting from 1. - * - *

The binding remains in effect until it is cleared or another value is bound to the same - * parameter. - * - *

Must not be called after {@link #executeQuery} returns a {@link Result} and before the - * {@link Result} is closed. - */ - public void bindString(int i, String val) throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "bindString() called in invalid state"); - checkNotNull(val); - bindStmtString(stmtPtr, i, val); - } - - /** - * Clears the i-th binding. - * - *

Must not be called after {@link #executeQuery} returns a {@link Result} and before the - * {@link Result} is closed. - */ - public void clearBinding(int i) throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "clearBinding() called in invalid state"); - clearStmtBinding(stmtPtr, i); - } - - /** - * Clears all bindings. - * - *

Must not be called after {@link #executeQuery} returns a {@link Result} and before the - * {@link Result} is closed. - */ - public void clearBindings() throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "clearBindings() called in invalid state"); - clearStmtBindings(stmtPtr); - } - - /** - * Executes a statement. - * - *

Execution doesn't actually start until the first call to {@link Result#next}. - * - *

Must not be called again until the returned {@link Result} has been closed. - */ - public Result executeQuery() { - checkState(stmtPtr != 0 && currentResult == null, "executeQuery() called in invalid state"); - currentResult = new Result(this); - return currentResult; - } - - /** - * Executes a statement to completion and discards its result. - * - *

For statements whose result is of interest, use {@link #executeQuery}. - * - *

Must not be called after {@link #executeQuery} until the returned {@link Result} has been - * closed. - * - * @throws IOException if an execution error occurred - */ - public void executeUpdate() throws IOException { - checkState(stmtPtr != 0 && currentResult == null, "executeUpdate() called in invalid state"); - currentResult = new Result(this); - try { - while (currentResult.next()) {} - } finally { - currentResult.close(); - } - } - } - - /** - * The result of executing a statement. - * - *

Acts as a cursor to iterate over result rows and obtain the corresponding column values. The - * cursor is initially positioned before the first row. A call to {@link #next} moves the cursor - * to the next row, returning {@code false} once no more results are available. If a call to - * {@link #next} returns {@code true}, the getter methods may be called to retrieve the column - * values for the current row. - */ - public static final class Result implements AutoCloseable { - - // The statement owning this result. - private Statement stmt; - - enum State { - START, // next() not yet called - CONTINUE, // last call to next() returned true - DONE, // last call to next() returned false, but close() not yet called - ERROR, // last call to next() threw, but close() not yet called - CLOSED // close() was called - } - - private State state = State.START; - - private Result(Statement stmt) { - this.stmt = stmt; - } - - /** - * Closes the result, rendering it unusable. - * - *

Multiple calls have no additional effect. - * - * @throws IOException if an error occurred while finishing execution - */ - @Override - public void close() throws IOException { - if (state != State.CLOSED) { - try { - resetStmt(stmt.stmtPtr); - } catch (IOException e) { - // Some statements may throw an error only after the result has been fully consumed. - // However, if the error has already been thrown by next(), don't throw it again. - if (state != State.ERROR) { - throw e; - } - } finally { - stmt.currentResult = null; - stmt = null; - state = State.CLOSED; - } - } - } - - /** - * Advances the cursor the next row. - * - *

Must not be called further after {@code false} is returned or an exception is thrown. - * - * @return whether another row was available - * @throws IOException if an error occurred while executing the statement - */ - public boolean next() throws IOException { - checkState(state == State.START || state == State.CONTINUE, "next() called in invalid state"); - - try { - boolean available = stepStmt(stmt.stmtPtr); - state = available ? State.CONTINUE : State.DONE; - return available; - } catch (IOException e) { - state = State.ERROR; - throw e; - } - } - - /** - * Returns whether the i-th column for the current result row is null. - * - *

WARNING: this must be called before any of the getter methods. Calling a getter method may - * cause a conversion to occur, after which the return value of this method is unspecified. - */ - public boolean isNull(int i) throws IOException { - checkState(state == State.CONTINUE, "isNull() called in invalid state"); - return columnIsNull(stmt.stmtPtr, i); - } - - /** - * Reads the i-th column for the current result row, starting from 0, as a long. - * - *

If the column is not of long type, a conversion occurs. In particular, null is converted - * to 0. - * - *

Must not be called unless the last call to {@link #next} returned {@code true}. - */ - public long getLong(int i) throws IOException { - checkState(state == State.CONTINUE, "getLong() called in invalid state"); - return columnLong(stmt.stmtPtr, i); - } - - /** - * Reads the i-th column for the current result row, starting from 0, as a double. - * - *

If the column is not of double type, a conversion occurs. In particular, null is converted - * to 0.0. - * - *

Must not be called unless the last call to {@link #next} returned {@code true}. - */ - public double getDouble(int i) throws IOException { - checkState(state == State.CONTINUE, "getDouble() called in invalid state"); - return columnDouble(stmt.stmtPtr, i); - } - - /** - * Reads the i-th column for the current result row, starting from 0, as a string. - * - *

If the column is not of string type, a conversion occurs. In particular, null is converted - * to the empty string. - * - *

Must not be called unless the last call to {@link #next} returned {@code true}. - */ - public String getString(int i) throws IOException { - checkState(state == State.CONTINUE, "getString() called in invalid state"); - return columnString(stmt.stmtPtr, i); - } - } - - private static native String errStr(int code); - - private static native long openConn(String path) throws IOException; - - private static native void closeConn(long conn) throws IOException; - - private static native long prepareStmt(long conn, String sql) throws IOException; - - private static native void bindStmtLong(long stmt, int i, long value) throws IOException; - - private static native void bindStmtDouble(long stmt, int i, double value) throws IOException; - - private static native void bindStmtString(long stmt, int i, String value) throws IOException; - - private static native void clearStmtBinding(long stmt, int i) throws IOException; - - private static native void clearStmtBindings(long stmt) throws IOException; - - private static native boolean stepStmt(long stmt) throws IOException; - - private static native boolean columnIsNull(long stmt, int i) throws IOException; - - private static native long columnLong(long stmt, int i) throws IOException; - - private static native double columnDouble(long stmt, int i) throws IOException; - - private static native String columnString(long stmt, int i) throws IOException; - - private static native void resetStmt(long stmt) throws IOException; - - private static native void finalizeStmt(long stmt) throws IOException; -} diff --git a/src/main/native/BUILD b/src/main/native/BUILD index 31f6b1c560df2b..c011aef6d20c2b 100644 --- a/src/main/native/BUILD +++ b/src/main/native/BUILD @@ -69,23 +69,6 @@ cc_library( alwayslink = 1, ) -cc_library( - name = "sqlite_jni", - srcs = [ - "sqlite_jni.cc", - ":jni.h", - ":jni_md.h", - ], - includes = ["."], # For jni headers. - visibility = ["//src/main/native:__subpackages__"], - deps = [ - ":latin1_jni_path", - "//src/main/cpp/util:logging", - "@sqlite3", - ], - alwayslink = 1, -) - cc_binary( name = "libunix_jni.so", srcs = [ @@ -114,7 +97,6 @@ cc_binary( deps = [ ":blake3_jni", ":latin1_jni_path", - ":sqlite_jni", "//src/main/cpp/util:logging", "//src/main/cpp/util:md5", "//src/main/cpp/util:port", diff --git a/src/main/native/sqlite_jni.cc b/src/main/native/sqlite_jni.cc deleted file mode 100644 index f4ff16a43e82a6..00000000000000 --- a/src/main/native/sqlite_jni.cc +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2024 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "src/main/cpp/util/logging.h" -#include "src/main/native/latin1_jni_path.h" -#include "sqlite3.h" - -namespace blaze_jni { - -namespace { - -// A RAII wrapper around a null-terminated UTF string obtained from a jstring. -class ScopedUTFString { - public: - ScopedUTFString(JNIEnv *env, jstring jstr) - : env_(env), - jstr_(jstr), - str_(env->GetStringUTFChars(jstr, nullptr)), - len_(env->GetStringUTFLength(jstr)) {} - - ~ScopedUTFString() { env_->ReleaseStringUTFChars(jstr_, str_); } - - const char *c_str() const { return str_; } - - int length() const { return len_; } - - private: - JNIEnv *env_; - jstring jstr_; - const char *str_; - int len_; -}; - -// Throws an exception for the given SQLite error code. -void PostException(JNIEnv *env, int err) { - jclass exc_class = env->FindClass( - "com/google/devtools/build/lib/remote/disk/Sqlite$SqliteException"); - if (exc_class == nullptr) { - BAZEL_LOG(FATAL) << "Failed to throw SQLite exception from JNI"; - } - jmethodID exc_ctor = env->GetMethodID(exc_class, "", "(I)V"); - if (exc_ctor == nullptr) { - BAZEL_LOG(FATAL) << "Failed to throw SQLite exception from JNI"; - } - jthrowable exc = - static_cast(env->NewObject(exc_class, exc_ctor, err)); - if (env->Throw(exc)) { - BAZEL_LOG(FATAL) << "Failed to throw SQLite exception from JNI"; - } -} - -} // namespace - -extern "C" JNIEXPORT jstring JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_errStr(JNIEnv *env, - jclass cls, - jint err) { - const char *str = sqlite3_errstr(err); - if (str == nullptr) { - str = "unknown error"; - } - return env->NewStringUTF(str); -} - -extern "C" JNIEXPORT jlong JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_openConn( - JNIEnv *env, jclass cls, jstring path_str) { - const char *path = GetStringLatin1Chars(env, path_str); - sqlite3 *conn = nullptr; - int err = sqlite3_open(path, &conn); - if (err != SQLITE_OK) { - PostException(env, err); - } - ReleaseStringLatin1Chars(path); - return reinterpret_cast(conn); -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_closeConn( - JNIEnv *env, jclass cls, jlong conn_ptr) { - sqlite3 *conn = reinterpret_cast(conn_ptr); - int err = sqlite3_close(conn); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT jlong JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_prepareStmt( - JNIEnv *env, jclass cls, jlong conn_ptr, jstring sql_jstr) { - sqlite3 *conn = reinterpret_cast(conn_ptr); - ScopedUTFString sql(env, sql_jstr); - sqlite3_stmt *stmt; - const char *sql_tail; - int err = sqlite3_prepare_v3(conn, sql.c_str(), sql.length(), - SQLITE_PREPARE_PERSISTENT, &stmt, &sql_tail); - if (err == SQLITE_OK && *sql_tail != '\0') { - // Return special value to signal an unsupported multi-statement string. - sqlite3_finalize(stmt); - return -1L; - } - if (err != SQLITE_OK) { - PostException(env, err); - } - return reinterpret_cast(stmt); -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_bindStmtLong( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i, jlong val) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_bind_int64(stmt, i, val); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_bindStmtDouble( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i, jdouble val) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_bind_double(stmt, i, val); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_bindStmtString( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i, jstring val_jstr) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - ScopedUTFString val(env, val_jstr); - int err = - sqlite3_bind_text(stmt, i, val.c_str(), val.length(), SQLITE_TRANSIENT); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_clearStmtBinding( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_bind_null(stmt, i); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_clearStmtBindings( - JNIEnv *env, jclass cls, jlong stmt_ptr) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_clear_bindings(stmt); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_stepStmt(JNIEnv *env, - jclass cls, - jlong stmt_ptr) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_step(stmt); - // Deviation from the C API: SQLITE_ROW and SQLITE_DONE are returned as true - // or false, respectively. Everything else causes an exception to be thrown. - if (err == SQLITE_ROW) { - return true; - } else if (err == SQLITE_DONE) { - return false; - } - PostException(env, err); - return false; -} - -extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_columnIsNull( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - return sqlite3_column_type(stmt, i) == SQLITE_NULL; -} - -extern "C" JNIEXPORT jlong JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_columnLong(JNIEnv *env, - jclass cls, - jlong stmt_ptr, - jint i) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - return sqlite3_column_int64(stmt, i); -} - -extern "C" JNIEXPORT jdouble JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_columnDouble( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - return sqlite3_column_double(stmt, i); -} - -extern "C" JNIEXPORT jstring JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_columnString( - JNIEnv *env, jclass cls, jlong stmt_ptr, jint i) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - const char *val = - reinterpret_cast(sqlite3_column_text(stmt, i)); - return val != nullptr ? env->NewStringUTF(val) : env->NewStringUTF(""); -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_resetStmt( - JNIEnv *env, jclass cls, jlong stmt_ptr) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_reset(stmt); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_google_devtools_build_lib_remote_disk_Sqlite_finalizeStmt( - JNIEnv *env, jclass cls, jlong stmt_ptr) { - sqlite3_stmt *stmt = reinterpret_cast(stmt_ptr); - int err = sqlite3_finalize(stmt); - if (err != SQLITE_OK) { - PostException(env, err); - } -} - -} // namespace blaze_jni diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD index c7e3de495b0635..b595e7553353b2 100644 --- a/src/main/native/windows/BUILD +++ b/src/main/native/windows/BUILD @@ -77,7 +77,6 @@ cc_binary( ":lib-file", ":lib-process", "//src/main/native:blake3_jni", - "//src/main/native:sqlite_jni", ], ) diff --git a/src/test/java/com/google/devtools/build/lib/remote/disk/SqliteTest.java b/src/test/java/com/google/devtools/build/lib/remote/disk/SqliteTest.java deleted file mode 100644 index bcb2aea3ff2082..00000000000000 --- a/src/test/java/com/google/devtools/build/lib/remote/disk/SqliteTest.java +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2024 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package com.google.devtools.build.lib.remote.disk; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.google.devtools.build.lib.remote.disk.Sqlite.Connection; -import com.google.devtools.build.lib.remote.disk.Sqlite.Result; -import com.google.devtools.build.lib.remote.disk.Sqlite.SqliteException; -import com.google.devtools.build.lib.remote.disk.Sqlite.Statement; -import com.google.devtools.build.lib.testutil.TestUtils; -import com.google.devtools.build.lib.vfs.Path; -import java.io.IOException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class SqliteTest { - - private Path dbPath; - - @Before - public void setUp() throws Exception { - dbPath = TestUtils.createUniqueTmpDir(null).getChild("tmp.db"); - } - - @After - public void tearDown() throws Exception { - try { - dbPath.delete(); - } catch (IOException e) { - // Intentionally ignored. - } - } - - @Test - public void executeQuery_withEmptyResult_nextReturnsFalse() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1 LIMIT 0"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isFalse(); - } - } - - @Test - public void executeQuery_withNonEmptyResult_nextReturnsTrue() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.next()).isTrue(); - assertThat(result.next()).isTrue(); - assertThat(result.next()).isFalse(); - } - } - - @Test - public void executeQuery_getterCalledBeforeNextReturnsTrue_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1"); - Result result = stmt.executeQuery()) { - assertThrows(IllegalStateException.class, () -> result.isNull(0)); - assertThrows(IllegalStateException.class, () -> result.getLong(0)); - assertThrows(IllegalStateException.class, () -> result.getDouble(0)); - assertThrows(IllegalStateException.class, () -> result.getString(0)); - } - } - - @Test - public void executeQuery_getterCalledAfterNextReturnsFalse_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1 LIMIT 0"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isFalse(); - assertThrows(IllegalStateException.class, () -> result.isNull(0)); - assertThrows(IllegalStateException.class, () -> result.getLong(0)); - assertThrows(IllegalStateException.class, () -> result.getDouble(0)); - assertThrows(IllegalStateException.class, () -> result.getString(0)); - } - } - - @Test - public void executeQuery_prematureClose_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1"); - Result result = stmt.executeQuery()) {} - } - - @Test - public void executeQuery_gettersConvertNull() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT NULL, NULL, NULL"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.isNull(0)).isTrue(); - assertThat(result.getLong(0)).isEqualTo(0); - assertThat(result.isNull(1)).isTrue(); - assertThat(result.getDouble(1)).isEqualTo(0.0); - assertThat(result.isNull(2)).isTrue(); - assertThat(result.getString(2)).isEmpty(); - } - } - - @Test - public void executeQuery_onError_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("CREATE TABLE tbl (id INTEGER)")) { - try (Result result = stmt.executeQuery()) { - assertThat(result.next()).isFalse(); - } - try (Result result = stmt.executeQuery()) { - SqliteException e = assertThrows(SqliteException.class, result::next); - assertThat(e).hasMessageThat().contains("SQL logic error"); - assertThat(e.getCode()).isEqualTo(Sqlite.ERR_ERROR); - } - } - } - - @Test - public void executeUpdate_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - try (Statement stmt = conn.newStatement("CREATE TABLE tbl (id INTEGER)")) { - stmt.executeUpdate(); - } - - try (Statement stmt = conn.newStatement("SELECT COUNT(*) FROM tbl"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - } - } - } - - @Test - public void executeUpdate_oneShot_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - conn.executeUpdate("CREATE TABLE tbl (id INTEGER)"); - - try (Statement stmt = conn.newStatement("SELECT COUNT(*) FROM tbl"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - } - } - } - - @Test - public void executeUpdate_withParameters_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - try (Statement stmt = conn.newStatement("CREATE TABLE tbl AS SELECT ?, ?, ?")) { - stmt.bindLong(1, 42); - stmt.bindDouble(2, 3.14); - stmt.bindString(3, "hello"); - stmt.executeUpdate(); - } - - try (Statement stmt = conn.newStatement("SELECT * FROM tbl"); - Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.isNull(0)).isFalse(); - assertThat(result.getLong(0)).isEqualTo(42); - assertThat(result.isNull(1)).isFalse(); - assertThat(result.getDouble(1)).isEqualTo(3.14); - assertThat(result.isNull(2)).isFalse(); - assertThat(result.getString(2)).isEqualTo("hello"); - assertThat(result.next()).isFalse(); - } - } - } - - @Test - public void executeUpdate_withNonEmptyResult_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT NULL")) {} - } - - @Test - public void executeUpdate_onError_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("CREATE TABLE tbl (id INTEGER)")) { - stmt.executeUpdate(); - SqliteException e = assertThrows(SqliteException.class, stmt::executeUpdate); - assertThat(e).hasMessageThat().contains("SQL logic error"); - assertThat(e.getCode()).isEqualTo(Sqlite.ERR_ERROR); - } - } - - @Test - public void clearBinding_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT ?, ?")) { - stmt.bindString(1, "abc"); - stmt.bindString(2, "def"); - - stmt.clearBinding(1); - try (Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.isNull(0)).isTrue(); - assertThat(result.isNull(1)).isFalse(); - assertThat(result.next()).isFalse(); - } - - stmt.clearBinding(2); - try (Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.isNull(0)).isTrue(); - assertThat(result.isNull(1)).isTrue(); - assertThat(result.next()).isFalse(); - } - } - } - - @Test - public void clearBindings_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT ?, ?")) { - stmt.bindString(1, "abc"); - stmt.bindString(2, "def"); - stmt.clearBindings(); - - try (Result result = stmt.executeQuery()) { - assertThat(result.next()).isTrue(); - assertThat(result.isNull(0)).isTrue(); - assertThat(result.isNull(1)).isTrue(); - assertThat(result.next()).isFalse(); - } - } - } - - @Test - public void newStatement_withInvalidStatement_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - SqliteException e = - assertThrows(SqliteException.class, () -> conn.newStatement("i am not sql")); - assertThat(e).hasMessageThat().contains("SQL logic error"); - assertThat(e.getCode()).isEqualTo(Sqlite.ERR_ERROR); - } - } - - @Test - public void newStatement_withTrailingSemicolon_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1;")) {} - } - - @Test - public void newStatement_withMultipleStatements_throws() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - IOException e = - assertThrows(IOException.class, () -> conn.newStatement("SELECT 1; SELECT 2")); - assertThat(e).hasMessageThat().contains("unsupported multi-statement string"); - } - } - - @Test - public void closeStatement_withOpenResult_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath); - Statement stmt = conn.newStatement("SELECT 1")) { - Result unusedResult = stmt.executeQuery(); - } - } - - @Test - public void multipleConnections_onBusy_throws_onRetry_works() throws Exception { - try (Connection conn1 = Sqlite.newConnection(dbPath); - Connection conn2 = Sqlite.newConnection(dbPath)) { - conn1.executeUpdate("BEGIN"); - conn1.executeUpdate("CREATE TABLE IF NOT EXISTS tbl (id INTEGER)"); - conn2.executeUpdate("BEGIN"); - SqliteException e = - assertThrows( - SqliteException.class, - () -> conn2.executeUpdate("CREATE TABLE IF NOT EXISTS tbl (id INTEGER)")); - assertThat(e).hasMessageThat().contains("database is locked"); - assertThat(e.getCode()).isEqualTo(Sqlite.ERR_BUSY); - conn1.executeUpdate("COMMIT"); - conn2.executeUpdate("CREATE TABLE IF NOT EXISTS tbl (id INTEGER)"); - conn2.executeUpdate("COMMIT"); - } - } - - @Test - public void closeConnection_withOpenStatement_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - Statement unusedStmt = conn.newStatement("SELECT 1"); - } - } - - @Test - public void closeConnection_withOpenStatementAndResult_works() throws Exception { - try (Connection conn = Sqlite.newConnection(dbPath)) { - Statement stmt = conn.newStatement("SELECT 1"); - Result unusedResult = stmt.executeQuery(); - } - } -}