diff --git a/conn.go b/conn.go index 93d6385..ad30ed8 100644 --- a/conn.go +++ b/conn.go @@ -194,6 +194,32 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str return stmt, tail, nil } +// DBName returns the schema name for n-th database on the database connection. +// +// https://sqlite.org/c3ref/db_name.html +func (c *Conn) DBName(n int) string { + r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n)) + + ptr := uint32(r) + if ptr == 0 { + return "" + } + return util.ReadString(c.mod, ptr, _MAX_NAME) +} + +// ReadOnly determines if a database is read-only. +// +// https://sqlite.org/c3ref/db_readonly.html +func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) { + var ptr uint32 + if schema != "" { + defer c.arena.mark()() + ptr = c.arena.string(schema) + } + r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr)) + return int8(r) > 0, int8(r) < 0 +} + // GetAutocommit tests the connection for auto-commit mode. // // https://sqlite.org/c3ref/get_autocommit.html @@ -211,6 +237,14 @@ func (c *Conn) LastInsertRowID() int64 { return int64(r) } +// SetLastInsertRowID allows the application to set the value returned by +// [Conn.LastInsertRowID]. +// +// https://sqlite.org/c3ref/set_last_insert_rowid.html +func (c *Conn) SetLastInsertRowID(id int64) { + c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id)) +} + // Changes returns the number of rows modified, inserted or deleted // by the most recently completed INSERT, UPDATE or DELETE statement // on the database connection. @@ -221,6 +255,24 @@ func (c *Conn) Changes() int64 { return int64(r) } +// TotalChanges returns the number of rows modified, inserted or deleted +// by all INSERT, UPDATE or DELETE statements completed +// since the database connection was opened. +// +// https://sqlite.org/c3ref/total_changes.html +func (c *Conn) TotalChanges() int64 { + r := c.call("sqlite3_total_changes64", uint64(c.handle)) + return int64(r) +} + +// ReleaseMemory frees memory used by a database connection. +// +// https://sqlite.org/c3ref/db_release_memory.html +func (c *Conn) ReleaseMemory() error { + r := c.call("sqlite3_db_release_memory", uint64(c.handle)) + return c.error(r) +} + // SetInterrupt interrupts a long-running query when a context is done. // // Subsequent uses of the connection will return [INTERRUPT] diff --git a/const.go b/const.go index 31f1972..2210210 100644 --- a/const.go +++ b/const.go @@ -229,6 +229,17 @@ const ( DBCONFIG_REVERSE_SCANORDER DBConfig = 1019 ) +// TxnState are the allowed return values from [Conn.TxnState]. +// +// https://sqlite.org/c3ref/c_txn_none.html +type TxnState uint32 + +const ( + TXN_NONE TxnState = 0 + TXN_READ TxnState = 1 + TXN_WRITE TxnState = 2 +) + // Datatype is a fundamental datatype of SQLite. // // https://sqlite.org/c3ref/c_blob.html diff --git a/embed/exports.txt b/embed/exports.txt index ed7b81d..7a556f8 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -1,7 +1,6 @@ free malloc malloc_destructor -sqlite3_aggregate_context sqlite3_anycollseq_init sqlite3_backup_finish sqlite3_backup_init @@ -47,6 +46,9 @@ sqlite3_create_function_go sqlite3_create_module_go sqlite3_create_window_function_go sqlite3_db_config +sqlite3_db_name +sqlite3_db_readonly +sqlite3_db_release_memory sqlite3_declare_vtab sqlite3_errcode sqlite3_errmsg @@ -58,6 +60,7 @@ sqlite3_get_autocommit sqlite3_get_auxdata sqlite3_interrupt sqlite3_last_insert_rowid +sqlite3_limit sqlite3_open_v2 sqlite3_overload_function sqlite3_prepare_v3 @@ -76,13 +79,15 @@ sqlite3_result_text64 sqlite3_result_value sqlite3_result_zeroblob64 sqlite3_set_auxdata_go +sqlite3_set_last_insert_rowid sqlite3_step sqlite3_stmt_busy sqlite3_stmt_readonly sqlite3_stmt_status +sqlite3_total_changes64 +sqlite3_txn_state sqlite3_uri_key sqlite3_uri_parameter -sqlite3_user_data sqlite3_value_blob sqlite3_value_bytes sqlite3_value_double diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 776623d..84f6e81 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/tests/blob_test.go b/tests/blob_test.go index 1aceae8..62b77b7 100644 --- a/tests/blob_test.go +++ b/tests/blob_test.go @@ -371,6 +371,12 @@ func TestBlob_Reopen(t *testing.T) { } rowids = append(rowids, db.LastInsertRowID()) } + if changes := db.Changes(); changes != 1 { + t.Errorf("got %d want 1", changes) + } + if changes := db.TotalChanges(); changes != 100 { + t.Errorf("got %d want 100", changes) + } var blob *sqlite3.Blob diff --git a/tests/conn_test.go b/tests/conn_test.go index 303d476..ed1fda8 100644 --- a/tests/conn_test.go +++ b/tests/conn_test.go @@ -364,3 +364,71 @@ func TestConn_ConfigLog(t *testing.T) { t.Error("want sqlite3.ERROR") } } + +func TestConn_ReleaseMemory(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + err = db.ReleaseMemory() + if err != nil { + t.Fatal(err) + } +} + +func TestConn_SetLastInsertRowID(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + db.SetLastInsertRowID(42) + + got := db.LastInsertRowID() + if got != 42 { + t.Errorf("got %d, want 42", got) + } +} + +func TestConn_ReadOnly(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + if ro, ok := db.ReadOnly(""); ro != false || ok != false { + t.Errorf("got %v,%v", ro, ok) + } + + if ro, ok := db.ReadOnly("xpto"); ro != false || ok != true { + t.Errorf("got %v,%v", ro, ok) + } +} + +func TestConn_DBName(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + if name := db.DBName(0); name != "main" { + t.Errorf("got %s", name) + } + + if name := db.DBName(5); name != "" { + t.Errorf("got %s", name) + } +} diff --git a/tests/tx_test.go b/tests/tx_test.go index a36d9b8..61fed91 100644 --- a/tests/tx_test.go +++ b/tests/tx_test.go @@ -47,6 +47,10 @@ func TestConn_Transaction_exec(t *testing.T) { t.Fatal(err) } + if s := db.TxnState("main"); s != sqlite3.TXN_WRITE { + t.Errorf("got %d", s) + } + if succeed { return nil } diff --git a/tx.go b/tx.go index f9996e4..e891006 100644 --- a/tx.go +++ b/tx.go @@ -213,3 +213,16 @@ func (c *Conn) txExecInterrupted(sql string) error { } return err } + +// TxnState starts a deferred transaction. +// +// https://sqlite.org/c3ref/txn_state.html +func (c *Conn) TxnState(schema string) TxnState { + var ptr uint32 + if schema != "" { + defer c.arena.mark()() + ptr = c.arena.string(schema) + } + r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr)) + return TxnState(r) +}