Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions packages/graalvm/api/graalvm.api
Original file line number Diff line number Diff line change
Expand Up @@ -7773,6 +7773,12 @@ public synthetic class elide/runtime/intrinsics/sqlite/$SQLiteAPI$ReflectConfig
public fun getAnnotationMetadata ()Lio/micronaut/core/annotation/AnnotationMetadata;
}

public synthetic class elide/runtime/intrinsics/sqlite/$SQLiteChanges$ReflectConfig : io/micronaut/core/graal/GraalReflectionConfigurer {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
public fun getAnnotationMetadata ()Lio/micronaut/core/annotation/AnnotationMetadata;
}

public synthetic class elide/runtime/intrinsics/sqlite/$SQLiteDatabase$Defaults$ReflectConfig : io/micronaut/core/graal/GraalReflectionConfigurer {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
Expand Down Expand Up @@ -7836,6 +7842,11 @@ public synthetic class elide/runtime/intrinsics/sqlite/$SQLiteTransactor$Reflect
public abstract interface class elide/runtime/intrinsics/sqlite/SQLiteAPI : org/graalvm/polyglot/proxy/ProxyObject {
}

public abstract interface class elide/runtime/intrinsics/sqlite/SQLiteChanges {
public abstract fun getChanges ()J
public abstract fun getLastInsertRowid ()J
}

public abstract interface class elide/runtime/intrinsics/sqlite/SQLiteDatabase : elide/runtime/interop/ReadOnlyProxyObject, elide/runtime/intrinsics/js/Disposable, java/io/Closeable, java/lang/AutoCloseable {
public static final field DEFAULT_CREATE Z
public static final field DEFAULT_READONLY Z
Expand All @@ -7847,8 +7858,8 @@ public abstract interface class elide/runtime/intrinsics/sqlite/SQLiteDatabase :
public abstract fun connection ()Lorg/sqlite/SQLiteConnection;
public abstract fun deserialize ([BLjava/lang/String;)V
public static synthetic fun deserialize$default (Lelide/runtime/intrinsics/sqlite/SQLiteDatabase;[BLjava/lang/String;ILjava/lang/Object;)V
public abstract fun exec (Lelide/runtime/intrinsics/sqlite/SQLiteStatement;[Ljava/lang/Object;)Lcom/oracle/truffle/js/runtime/objects/JSDynamicObject;
public abstract fun exec (Ljava/lang/String;[Ljava/lang/Object;)Lcom/oracle/truffle/js/runtime/objects/JSDynamicObject;
public abstract fun exec (Lelide/runtime/intrinsics/sqlite/SQLiteStatement;[Ljava/lang/Object;)Lelide/runtime/intrinsics/sqlite/SQLiteChanges;
public abstract fun exec (Ljava/lang/String;[Ljava/lang/Object;)Lelide/runtime/intrinsics/sqlite/SQLiteChanges;
public abstract fun getActive ()Z
public synthetic fun getMemberKeys ()Ljava/lang/Object;
public fun getMemberKeys ()[Ljava/lang/String;
Expand Down Expand Up @@ -7902,7 +7913,7 @@ public abstract interface class elide/runtime/intrinsics/sqlite/SQLiteStatement
public abstract fun get ([Ljava/lang/Object;)Lelide/runtime/intrinsics/sqlite/SQLiteObject;
public abstract fun prepare ([Ljava/lang/Object;)Ljava/sql/PreparedStatement;
public static synthetic fun prepare$default (Lelide/runtime/intrinsics/sqlite/SQLiteStatement;[Ljava/lang/Object;ILjava/lang/Object;)Ljava/sql/PreparedStatement;
public abstract fun run ([Ljava/lang/Object;)V
public abstract fun run ([Ljava/lang/Object;)Lelide/runtime/intrinsics/sqlite/SQLiteChanges;
public abstract fun unwrap ()Ljava/sql/Statement;
public abstract fun values ([Ljava/lang/Object;)Ljava/util/List;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,27 @@ internal class SqliteDatabaseProxy private constructor (
}
}

// Internal SQLiteChanges implementation
private data class SQLiteChangesImpl(
override val changes: Long,
override val lastInsertRowid: Long,
) : SQLiteChanges, ProxyObject {
override fun getMemberKeys(): Array<String> = arrayOf("changes", "lastInsertRowid")
override fun hasMember(key: String): Boolean = key == "changes" || key == "lastInsertRowid"
override fun getMember(key: String): Any? = when (key) {
"changes" -> changes
"lastInsertRowid" -> lastInsertRowid
else -> null
}
override fun putMember(key: String?, value: Value?) {
throw UnsupportedOperationException("SQLiteChanges is immutable")
}

companion object {
val EMPTY = SQLiteChangesImpl(0, 0)
}
}

// Internal SQLite object implementation, backed by a de-serialized map.
private data class SQLiteObjectImpl private constructor (
private val schema: SQLiteObjectSchema,
Expand Down Expand Up @@ -430,8 +451,8 @@ internal class SqliteDatabaseProxy private constructor (
resultSet.firstOrNull()
}

@Polyglot override fun run(vararg args: Any?) {
requireNotNull(db.get()) { "Database is closed" }.exec(this, *args)
@Polyglot override fun run(vararg args: Any?): SQLiteChanges {
return requireNotNull(db.get()) { "Database is closed" }.exec(this, *args)
}

@Polyglot @JvmSynthetic override fun finalize() {
Expand Down Expand Up @@ -579,10 +600,11 @@ internal class SqliteDatabaseProxy private constructor (
override fun connection(): SQLiteConnection = withOpen { this }
override fun unwrap(): DB = withOpen { database }

@Polyglot override fun loadExtension(extension: String) = withOpen {
@Polyglot override fun loadExtension(extension: String): JSDynamicObject = withOpen {
unwrap().enable_load_extension(true)
require(';' !in extension && ' ' !in extension) { "Invalid extension name" } // sanity check
exec("SELECT load_extension('$extension')")
Undefined.instance
}

@Polyglot override fun prepare(statement: String, vararg args: Any?): Statement = withOpen {
Expand All @@ -603,15 +625,23 @@ internal class SqliteDatabaseProxy private constructor (
}
}

@Polyglot override fun exec(statement: String, vararg args: Any?): JSDynamicObject = withOpen {
@Polyglot override fun exec(statement: String, vararg args: Any?): SQLiteChanges = withOpen {
exec(oneShotStatement(statement, args))
}

@Polyglot override fun exec(statement: Statement, vararg args: Any?): JSDynamicObject = withOpen {
statement.prepare(args).use {
it.execute()
@Polyglot override fun exec(statement: Statement, vararg args: Any?): SQLiteChanges = withOpen {
val prepared = statement.prepare(args)
prepared.use { stmt ->
stmt.execute()
val updateCount = stmt.updateCount.toLong()
// Get last insert rowid via SQLite function
val lastRowId = connection().prepareStatement("SELECT last_insert_rowid()").use { rowIdStmt ->
rowIdStmt.executeQuery().use { rs ->
if (rs.next()) rs.getLong(1) else 0L
}
}
SQLiteChangesImpl(updateCount, lastRowId)
}
Undefined.instance
}

@Polyglot override fun <R> transaction(runnable: SQLiteTransactor<R>): SQLiteTransaction<R> = withOpen {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2024 Elide Technologies, Inc.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://opensource.org/license/mit/
*
* 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 elide.runtime.intrinsics.sqlite

import io.micronaut.core.annotation.ReflectiveAccess
import elide.annotations.API
import elide.vm.annotations.Polyglot

/**
* # SQLite Changes
*
* Represents the result of a write operation (INSERT, UPDATE, DELETE) on a SQLite database.
* This matches the Bun SQLite API's `Changes` interface.
*
* @see SQLiteStatement.run
* @see SQLiteDatabase.exec
*/
@API @ReflectiveAccess public interface SQLiteChanges {
/**
* The number of rows changed by the last `run` or `exec` call.
*/
@get:Polyglot public val changes: Long

/**
* The rowid of the last inserted row, or 0 if no row was inserted.
* If `safeIntegers` is enabled, this should be treated as a bigint.
*/
@get:Polyglot public val lastInsertRowid: Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,25 +196,23 @@ private val SQLITE_DATABASE_PROPS_AND_METHODS = arrayOf(
* Parse, prepare, and then execute an SQL query [statement] with the provided [args] (if any), against the current
* SQLite database.
*
* This method does not return a value.
*
* @param statement SQL query to execute.
* @param args Arguments to bind to the statement.
* @return Changes object containing the number of affected rows and last insert rowid.
*/
@Polyglot public fun exec(@Language("sql") statement: String, vararg args: Any?): JSDynamicObject
@Polyglot public fun exec(@Language("sql") statement: String, vararg args: Any?): SQLiteChanges

/**
* ## Execute (Statement)
*
* Execute the provided [statement], preparing it if necessary, with the provided [args] (if any), against the current
* SQLite database.
*
* This method does not return a value.
*
* @param statement Prepared statement to execute.
* @param args Arguments to bind to the statement.
* @return Changes object containing the number of affected rows and last insert rowid.
*/
@Polyglot public fun exec(statement: SQLiteStatement, vararg args: Any?): JSDynamicObject
@Polyglot public fun exec(statement: SQLiteStatement, vararg args: Any?): SQLiteChanges

/**
* ## Execute Transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,11 @@ import elide.vm.annotations.Polyglot
*
* Repeated calls to this method with unchanging [args] will cache the underlying rendered query, but will not cache
* execution of the query (in other words, the query is executed each time [run] is called).
*
* @param args Arguments to render into the query.
* @return Changes object containing the number of affected rows and last insert rowid.
*/
@Polyglot public fun run(vararg args: Any?)
@Polyglot public fun run(vararg args: Any?): SQLiteChanges

/**
* ## Finalize Statement
Expand Down
Loading