Files
Maven/vendor/modernc.org/sqlite/rows.go
kami 6c92f85d10 feat(ecosystem): compliant Praxis/Hexis integration + vendored build
Bring the Nexus/Praxis/Hexis integration in line with
MAVEN_ECOSYSTEM_ARCHITECTURE.md:

- Praxis over HTTP: drop the in-process praxis.db open (praxisstore/
  praxistools) and call praxisd's /api/v1/tools/* API via a new praxisClient.
  Honors the "no component reads another's DB" invariant (AC#12).
  PraxisConfig.DBPath -> URL.
- Hexis confirmation gate: mutating capabilities (ReadOnly=false) now park a
  bound pendingHexis confirmation and require a spoken "да" before executing;
  read-only run immediately (AC#7, no auto attention->action).
- Capability safety: >1 verb match is ambiguous -> ask instead of firing the
  first; ambiguous Nexus resolution asks for clarification (AC#2).
- Correlation IDs on Hexis execute, recorded in the cross-service trace.
- Bug: importance arrives as JSON float64 over HTTP, not int.
- Tests: confirm-gate, decline, read-only, and ambiguity paths.

Build: vendor/ bakes in the hexis client (replace-directed at a sibling repo
outside the Docker context); Dockerfile builds from vendor and no longer
`go mod download`s the unreachable replace paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:24:33 +04:00

354 lines
10 KiB
Go

// Copyright 2025 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite // import "modernc.org/sqlite"
import (
"database/sql/driver"
"fmt"
"io"
"math"
"reflect"
"strings"
"time"
sqlite3 "modernc.org/sqlite/lib"
)
type rows struct {
allocs []uintptr
c *conn
columns []string
// decltypes holds the uppercased declared types for every result column,
// captured once at newRows time. The declared type of a column is fixed
// for the lifetime of a prepared statement, so caching the result of
// strings.ToUpper(sqlite3_column_decltype(...)) here removes a per-row
// libc.GoString + strings.ToUpper from the Next() hot path for callers
// that hit the time-conversion branches (_texttotime, _time_format).
decltypes []string
// parseFmtIdx caches, per column, the index into parseTimeFormats that
// matched the first successful (*conn).parseTime call on that column.
// Subsequent rows reuse the saved index as the first attempt instead of
// re-walking the format list from the top. Slot value -1 means no match
// has been recorded yet (either parseTime has not run on this column, or
// the parseTimeString / m= branch matched, which is not in
// parseTimeFormats). The cache is sticky: once a successful index is
// stored it is not overwritten if a later row happens to match a
// different format. A steady-format column therefore wins on every row
// after the first; a mixed-format column falls through as before, paying
// at most one extra format probe on rows whose matching format precedes
// the cached one.
parseFmtIdx []int8
pstmt uintptr
doStep bool
empty bool
reuseStmt bool // If true, Close() resets instead of finalizing
}
func newRows(c *conn, pstmt uintptr, allocs *[]uintptr, empty bool) (r *rows, err error) {
var a []uintptr
if allocs != nil {
a = *allocs
*allocs = nil
}
r = &rows{c: c, pstmt: pstmt, allocs: a, empty: empty}
defer func() {
if err != nil {
r.Close()
r = nil
}
}()
n, err := c.columnCount(pstmt)
if err != nil {
return nil, err
}
r.columns = make([]string, n)
r.decltypes = make([]string, n)
r.parseFmtIdx = make([]int8, n)
for i := range r.columns {
if r.columns[i], err = r.c.columnName(pstmt, i); err != nil {
return nil, err
}
r.decltypes[i] = strings.ToUpper(r.c.columnDeclType(pstmt, i))
r.parseFmtIdx[i] = -1
}
return r, nil
}
// Close closes the rows iterator.
func (r *rows) Close() (err error) {
r.c.freeAllocs(r.allocs)
r.allocs = nil
if r.reuseStmt {
// Reset the statement for reuse instead of finalizing it
if e := r.c.reset(r.pstmt); e != nil {
return e
}
return r.c.clearBindings(r.pstmt)
}
return r.c.finalize(r.pstmt)
}
// Columns returns the names of the columns. The number of columns of the
// result is inferred from the length of the slice. If a particular column name
// isn't known, an empty string should be returned for that entry.
func (r *rows) Columns() (c []string) {
return r.columns
}
// Next is called to populate the next row of data into the provided slice. The
// provided slice will be the same size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
func (r *rows) Next(dest []driver.Value) (err error) {
if r.empty {
return io.EOF
}
rc := sqlite3.SQLITE_ROW
if r.doStep {
if rc, err = r.c.step(r.pstmt); err != nil {
return err
}
}
r.doStep = true
switch rc {
case sqlite3.SQLITE_ROW:
if g, e := len(dest), len(r.columns); g != e {
return fmt.Errorf("sqlite: Next: have %v destination values, expected %v", g, e)
}
for i := range dest {
ct, err := r.c.columnType(r.pstmt, i)
if err != nil {
return err
}
switch ct {
case sqlite3.SQLITE_INTEGER:
v, err := r.c.columnInt64(r.pstmt, i)
if err != nil {
return err
}
if !r.c.intToTime {
dest[i] = v
} else {
// Inspired by mattn/go-sqlite3:
// https://github.com/mattn/go-sqlite3/blob/f76bae4b0044cbba8fb2c72b8e4559e8fbcffd86/sqlite3.go#L2254-L2262
// but we put make this compatibility optional behind a DSN
// query parameter, because this changes API behavior, so an
// opt-in is needed.
switch r.ColumnTypeDatabaseTypeName(i) {
case "DATE", "DATETIME", "TIMESTAMP":
// Check for explicit opt-in first. This fixes the bug for micro/nano users
// without breaking the legacy heuristic for existing users.
switch r.c.integerTimeFormat {
case "unix_micro":
dest[i] = r.c.applyTimezone(time.UnixMicro(v).UTC())
continue
case "unix_nano":
dest[i] = r.c.applyTimezone(time.Unix(0, v).UTC())
continue
}
// Legacy Heuristic (mattn/go-sqlite3 compatibility). NOTE: This heuristic
// fails for Millisecond timestamps representing dates before Sept 9, 2001
// (value < 1e12).
// Is it a seconds timestamp or a milliseconds
// timestamp?
if v > 1e12 || v < -1e12 {
// Milliseconds
dest[i] = r.c.applyTimezone(time.UnixMilli(v).UTC())
} else {
// Seconds
dest[i] = r.c.applyTimezone(time.Unix(v, 0).UTC())
}
default:
dest[i] = v
}
}
case sqlite3.SQLITE_FLOAT:
v, err := r.c.columnDouble(r.pstmt, i)
if err != nil {
return err
}
dest[i] = v
case sqlite3.SQLITE_TEXT:
v, err := r.c.columnText(r.pstmt, i)
if err != nil {
return err
}
switch r.ColumnTypeDatabaseTypeName(i) {
case "DATE", "DATETIME", "TIMESTAMP":
val, ok, idx := r.c.parseTime(v, int(r.parseFmtIdx[i]))
if ok && r.parseFmtIdx[i] < 0 && idx >= 0 {
r.parseFmtIdx[i] = int8(idx)
}
dest[i] = val
default:
// A TEXT column with no declared type. SQLite reports an
// empty decltype not only for aggregates and expressions
// over a date column (MAX/MIN/COALESCE, upper(x), x||''),
// but also for subqueries and for typeless real columns
// (CREATE TABLE t(x)). Any of these would be delivered as
// a raw string that Scan cannot store into *time.Time
// (#248). Under _texttotime, best-effort parse the value:
// on success deliver time.Time, otherwise fall back to the
// original string, so no Scan that worked before can newly
// fail.
if r.c.textToTime && r.decltypes[i] == "" {
if val, ok, idx := r.c.parseTime(v, int(r.parseFmtIdx[i])); ok {
if r.parseFmtIdx[i] < 0 && idx >= 0 {
r.parseFmtIdx[i] = int8(idx)
}
dest[i] = val
continue
}
}
dest[i] = v
}
case sqlite3.SQLITE_BLOB:
v, err := r.c.columnBlob(r.pstmt, i)
if err != nil {
return err
}
dest[i] = v
case sqlite3.SQLITE_NULL:
dest[i] = nil
default:
return fmt.Errorf("internal error: rc %d", rc)
}
}
return nil
case sqlite3.SQLITE_DONE:
return io.EOF
default:
return r.c.errstr(int32(rc))
}
}
// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return
// the database system type name without the length. Type names should be
// uppercase. Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2",
// "CHAR", "TEXT", "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT",
// "JSONB", "XML", "TIMESTAMP".
func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
return r.decltypes[index]
}
// RowsColumnTypeLength may be implemented by Rows. It should return the length
// of the column type if the column is a variable length type. If the column is
// not a variable length type ok should return false. If length is not limited
// other than system limits, it should return math.MaxInt64. The following are
// examples of returned values for various types:
//
// TEXT (math.MaxInt64, true)
// varchar(10) (10, true)
// nvarchar(10) (10, true)
// decimal (0, false)
// int (0, false)
// bytea(30) (30, true)
func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) {
t, err := r.c.columnType(r.pstmt, index)
if err != nil {
return 0, false
}
switch t {
case sqlite3.SQLITE_INTEGER:
return 0, false
case sqlite3.SQLITE_FLOAT:
return 0, false
case sqlite3.SQLITE_TEXT:
return math.MaxInt64, true
case sqlite3.SQLITE_BLOB:
return math.MaxInt64, true
case sqlite3.SQLITE_NULL:
return 0, false
default:
return 0, false
}
}
// RowsColumnTypeNullable may be implemented by Rows. The nullable value should
// be true if it is known the column may be null, or false if the column is
// known to be not nullable. If the column nullability is unknown, ok should be
// false.
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
return true, true
}
// RowsColumnTypePrecisionScale may be implemented by Rows. It should return
// the precision and scale for decimal types. If not applicable, ok should be
// false. The following are examples of returned values for various types:
//
// decimal(38, 4) (38, 4, true)
// int (0, 0, false)
// decimal (math.MaxInt64, math.MaxInt64, true)
func (r *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
return 0, 0, false
}
// RowsColumnTypeScanType may be implemented by Rows. It should return the
// value type that can be used to scan types into. For example, the database
// column type "bigint" this should return "reflect.TypeOf(int64(0))".
func (r *rows) ColumnTypeScanType(index int) reflect.Type {
t, err := r.c.columnType(r.pstmt, index)
if err != nil {
return reflect.TypeOf("")
}
switch t {
case sqlite3.SQLITE_INTEGER:
switch r.decltypes[index] {
case "BOOLEAN":
return reflect.TypeOf(false)
case "DATE", "DATETIME", "TIME", "TIMESTAMP":
return reflect.TypeOf(time.Time{})
default:
return reflect.TypeOf(int64(0))
}
case sqlite3.SQLITE_FLOAT:
return reflect.TypeOf(float64(0))
case sqlite3.SQLITE_TEXT:
if r.c.textToTime {
switch r.decltypes[index] {
case "DATE", "DATETIME", "TIME", "TIMESTAMP":
return reflect.TypeOf(time.Time{})
}
}
return reflect.TypeOf("")
case sqlite3.SQLITE_BLOB:
return reflect.TypeOf([]byte(nil))
case sqlite3.SQLITE_NULL:
return reflect.TypeOf(nil)
default:
return reflect.TypeOf("")
}
}
// C documentation
//
// int sqlite3_reset(sqlite3_stmt *pStmt);
func (c *conn) reset(pstmt uintptr) error {
if rc := sqlite3.Xsqlite3_reset(c.tls, pstmt); rc != sqlite3.SQLITE_OK {
return c.errstr(rc)
}
return nil
}