Compare commits

..

108 Commits

Author SHA1 Message Date
Nuno Cruces
4a956e80a2 wasi-sdk-28. 2025-11-09 01:32:25 +00:00
Nuno Cruces
5f4ff03f6f wazero v1.10.0. 2025-11-09 01:32:15 +00:00
Nuno Cruces
5890049488 Shim modernc. 2025-11-09 01:32:14 +00:00
Nuno Cruces
5e73c5d714 Issue #330. 2025-11-06 12:07:37 +00:00
Nuno Cruces
6d92aa16ef SQLite 3.51.0. 2025-11-05 12:30:09 +00:00
Nuno Cruces
191d1337e7 Gorm v1.31.1. 2025-11-05 12:30:09 +00:00
Nuno Cruces
b65e894849 Improve error reporting. (#327) 2025-10-17 16:40:15 +01:00
Nuno Cruces
0b040d3f09 Prepare 3.51.0. 2025-10-16 15:18:44 +01:00
Nuno Cruces
1db4366226 VFS error handling. 2025-10-16 15:18:40 +01:00
Nuno Cruces
9e1cbfb5bb Release snapshot. 2025-10-10 17:37:44 +01:00
dependabot[bot]
7f2d70a0f3 Bump golang.org/x/crypto from 0.42.0 to 0.43.0 (#324)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.42.0 to 0.43.0.
- [Commits](https://github.com/golang/crypto/compare/v0.42.0...v0.43.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-08 23:11:06 +01:00
Nuno Cruces
ea860e407d Docs. 2025-10-01 11:00:13 +01:00
Nuno Cruces
d4561d08f9 Refactor. 2025-10-01 10:48:54 +01:00
Nuno Cruces
14c1e490b4 Optimize fullfsync. 2025-09-30 12:54:18 +01:00
Nuno Cruces
23aad5f62f MVCC API. 2025-09-29 12:52:01 +01:00
Nuno Cruces
e5bd10a1ff Fix TestDB. 2025-09-29 12:44:09 +01:00
Nuno Cruces
5cf06c45f7 Scan improvements. 2025-09-24 18:13:26 +01:00
Nuno Cruces
08f9fc758a JSON experiment. 2025-09-24 18:13:21 +01:00
Nuno Cruces
b588d5f991 Error messages, test contexts. 2025-09-23 12:51:36 +01:00
Nuno Cruces
4c24bd0cb6 Verify download. 2025-09-23 11:49:59 +01:00
Nuno Cruces
cc353e4848 Time improvements. 2025-09-23 11:49:53 +01:00
Nuno Cruces
c3ebb04045 Use crypto/pbkdf2. 2025-09-18 18:41:10 +01:00
Nuno Cruces
11e064574c Weight-balanced trees. 2025-09-17 11:01:24 +01:00
Nuno Cruces
770420289a Updated dependencies. 2025-09-10 17:09:00 +01:00
Nuno Cruces
62f69011f1 Updated dependencies. 2025-09-08 13:59:41 +01:00
Nuno Cruces
4f9e3f900b binaryen-version_124. 2025-09-08 12:23:58 +01:00
dependabot[bot]
4e90618350 Bump actions/setup-go from 5 to 6 (#318)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 00:25:35 +02:00
Nuno Cruces
54bb94ce58 Improve WithMemoryCapacityFromMax (#317). 2025-09-03 09:03:59 +02:00
Nuno Cruces
07fec784e1 Grow memory geometrically. (#316) 2025-09-01 12:57:33 +02:00
dependabot[bot]
da4638cbff Bump actions/attest-build-provenance from 2 to 3 (#313)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2 to 3.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 00:19:53 +02:00
dependabot[bot]
085872c2f3 Bump github.com/ncruces/aa from 0.3.1 to 0.3.2 (#311)
Bumps [github.com/ncruces/aa](https://github.com/ncruces/aa) from 0.3.1 to 0.3.2.
- [Commits](https://github.com/ncruces/aa/compare/v0.3.1...v0.3.2)

---
updated-dependencies:
- dependency-name: github.com/ncruces/aa
  dependency-version: 0.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 17:20:13 +02:00
dependabot[bot]
de49aa2b06 Bump github.com/ncruces/aa from 0.3.0 to 0.3.1 (#310)
Bumps [github.com/ncruces/aa](https://github.com/ncruces/aa) from 0.3.0 to 0.3.1.
- [Commits](https://github.com/ncruces/aa/compare/v0.3.0...v0.3.1)

---
updated-dependencies:
- dependency-name: github.com/ncruces/aa
  dependency-version: 0.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-23 05:21:11 +01:00
Nuno Cruces
1f3ad0165e SQLite 3.50.4. 2025-08-21 19:05:44 +01:00
Nuno Cruces
0bda48d1d9 Gorm v1.30.1. 2025-08-21 18:56:05 +01:00
Nuno Cruces
0026bc91aa MVCC memory VFS. (#309) 2025-08-21 18:44:40 +01:00
Nuno Cruces
d84ca9d627 Fix #308. 2025-08-16 19:45:10 +01:00
Nuno Cruces
5d14e01f94 Fix #304. 2025-08-16 19:27:00 +01:00
Nuno Cruces
342df983d4 Fix #305. 2025-08-14 23:46:48 +01:00
Nuno Cruces
00476fb1e2 Tests. 2025-08-14 15:04:10 +01:00
Nuno Cruces
8a64ee6eaa Implement RowsColumnScanner. 2025-08-14 01:42:00 +01:00
Nuno Cruces
8f9a8e2752 Learnings from truffle. 2025-08-13 13:10:50 +01:00
Nuno Cruces
d8880e4cee Fixes. 2025-08-13 03:34:21 +01:00
dependabot[bot]
4b154a842c Bump actions/checkout from 4 to 5 (#303)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 08:47:41 +01:00
dependabot[bot]
758a53e9bf Bump golang.org/x/crypto from 0.40.0 to 0.41.0 (#300)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 00:30:25 +01:00
Nuno Cruces
1a42b4c590 Better fuzzing. 2025-08-07 16:27:45 +01:00
Nuno Cruces
7e4ec1df1c Fix #299. 2025-08-07 01:52:01 +01:00
Nuno Cruces
2c582a1d66 VFS improvements. 2025-08-05 14:15:21 +01:00
Nuno Cruces
20a67ca669 WAL mode serdes. 2025-08-02 11:48:37 +01:00
Nuno Cruces
789e2dc136 wasi-sdk-27. 2025-07-29 16:50:07 +01:00
Nuno Cruces
0399f10c06 VFS improvements. 2025-07-23 09:57:53 +01:00
Nuno Cruces
75c6744b5b FreeBSD 14.3. 2025-07-22 23:47:22 +01:00
Nuno Cruces
754e806164 Tests. 2025-07-22 10:34:30 +01:00
Nuno Cruces
2640c9fb54 SQLite 3.50.3. 2025-07-17 19:42:01 +01:00
Nuno Cruces
9719d4b0e3 Better tests. 2025-07-17 01:11:16 +01:00
Nuno Cruces
b21c69dc1f Fix mode. 2025-07-17 00:50:39 +01:00
Nuno Cruces
b0f8ff44a5 Support subtype. 2025-07-17 00:47:04 +01:00
Nuno Cruces
f37bca6a80 Speedup memdb. 2025-07-15 23:08:41 +01:00
Nuno Cruces
b4e8fcb752 Avoid UB. 2025-07-15 15:52:39 +01:00
dependabot[bot]
14b98a5d05 Bump golang.org/x/crypto from 0.39.0 to 0.40.0 (#295)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-11 00:30:01 +01:00
Nuno Cruces
36a62264f9 Remove unneeded generics. 2025-07-10 13:05:55 +01:00
Nuno Cruces
33ea564f38 Deps. 2025-07-10 00:50:14 +01:00
Nuno Cruces
5c55d8692f Fix bitset. 2025-07-04 17:10:40 +01:00
Nuno Cruces
be2f3036b4 SQLite 3.50.2. 2025-06-30 12:29:54 +01:00
Nuno Cruces
784f82f42f Avoid UB. 2025-06-25 15:27:11 +01:00
Nuno Cruces
cd6ba43e77 Less SIMD. 2025-06-24 02:23:54 +01:00
Nuno Cruces
d7aef63844 Naming, volatile. 2025-06-20 12:45:42 +01:00
Nuno Cruces
64e5046f10 Improved byteset search. 2025-06-17 11:36:53 +01:00
Nuno Cruces
0bdce8aa68 Avoid overflow. 2025-06-12 15:12:20 +01:00
Nuno Cruces
69a2881a10 SQLite 3.50.1. 2025-06-08 00:38:01 +01:00
dependabot[bot]
24ad4445f1 Bump golang.org/x/crypto from 0.38.0 to 0.39.0 (#285)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.38.0 to 0.39.0.
- [Commits](https://github.com/golang/crypto/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 23:49:59 +01:00
Nuno Cruces
c159bbd88f Docs, tweaks. 2025-06-04 12:19:01 +01:00
Nuno Cruces
c90f8205f7 Remove. 2025-06-03 12:54:56 +01:00
Nuno Cruces
b64b9b0415 Better strcasestr. 2025-06-02 10:25:10 +01:00
Nuno Cruces
9142e19d61 SQLite 3.50.0. 2025-05-31 01:14:16 +01:00
Daenney
4a76f2b064 README: Make the testing section simpler to read (#282) 2025-05-30 14:29:34 +01:00
Nuno Cruces
c9b364507e Avoid copy, alloc. 2025-05-27 11:35:43 +01:00
Nuno Cruces
2204b96ff6 Gorm. 2025-05-27 10:51:42 +01:00
Nuno Cruces
b46f480d79 FCNTL_NULL_IO. 2025-05-26 16:32:52 +01:00
Nuno Cruces
040a026925 More examples. 2025-05-26 12:26:14 +01:00
Michael Lynch
e678040a4e Remove duplicate link to 'custom VFSes' in README (#279) 2025-05-26 11:49:03 +01:00
Michael Lynch
f1cc12569c Link to example of incremental BLOB I/O usage (#280) 2025-05-26 11:48:53 +01:00
Nuno Cruces
721a987e0e Line numbers. 2025-05-26 11:48:02 +01:00
Michael Lynch
f3d65142cc Use sql.Named to clarify the blobio example (#281)
I find the example SQL queries a bit difficult to read at the callsite with the magic numbers and ? placeholders. I think sql.Named makes it more obvious to the reader what the different parts of the SQL query represent.
2025-05-25 19:02:19 +01:00
Nuno Cruces
93f711c77b More fuzzing. 2025-05-21 11:36:56 +01:00
Nuno Cruces
341bd063e8 More fuzzing. 2025-05-21 08:16:52 +01:00
Nuno Cruces
f765882670 Sunday's Quick Search. 2025-05-17 13:20:11 +01:00
Nuno Cruces
ff3676ff4a Case insensitive search, fixes. 2025-05-16 13:10:03 +01:00
Nuno Cruces
54877a53cd Differential fuzzing. 2025-05-15 14:55:34 +01:00
Nuno Cruces
fccc6c10a7 Issue #277. 2025-05-14 13:46:01 +01:00
Nuno Cruces
fc21ffcc71 Tweaks. 2025-05-14 01:10:44 +01:00
Nuno Cruces
687e643d7a Case insensitive compare. 2025-05-13 18:12:14 +01:00
Nuno Cruces
fc5ced209c Fix bcmp. 2025-05-13 14:28:21 +01:00
Nuno Cruces
c1bed07e3a Issue #276. 2025-05-13 14:27:04 +01:00
Nuno Cruces
a0771f2363 Relaxed SIMD. 2025-05-12 17:30:32 +01:00
Nuno Cruces
6bad547d3d Unknown haystack length. 2025-05-12 12:00:06 +01:00
Nuno Cruces
c2c1aea578 Handle repetitive needles. 2025-05-10 01:44:25 +01:00
Nuno Cruces
60ab485b29 Comments. 2025-05-09 14:21:23 +01:00
Nuno Cruces
e17a432fde Adds strstr and memmem. (#275) 2025-05-09 00:59:39 +01:00
Nuno Cruces
c780ef16e2 SQLite 3.49.2. 2025-05-07 14:08:18 +01:00
Nuno Cruces
b609930142 Refactor #274. 2025-05-07 12:46:13 +01:00
Nuno Cruces
fd165ce724 Issue #274. 2025-05-07 01:37:52 +01:00
Nuno Cruces
d3973b23e3 More memcmp. 2025-05-06 15:48:58 +01:00
Nuno Cruces
320b68e74f More tests. 2025-05-05 14:47:43 +01:00
Nuno Cruces
2c3850e5d1 Reuse fast funcs. 2025-05-03 01:18:10 +01:00
Nuno Cruces
db7aacff9f Add strrchr. 2025-05-02 14:35:14 +01:00
Nuno Cruces
d748d98e39 Fix. 2025-05-01 12:49:38 +01:00
Nuno Cruces
13b8642384 Compile as C++. 2025-04-29 14:03:59 +01:00
Nuno Cruces
29c5c816cb More libc. 2025-04-27 23:35:13 +01:00
168 changed files with 5452 additions and 3305 deletions

View File

@@ -10,12 +10,12 @@ jobs:
test:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15]
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, macos-15-intel]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Benchmark

View File

@@ -17,13 +17,13 @@ jobs:
steps:
- uses: ilammy/msvc-dev-cmd@v1
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Build
shell: bash
run: .github/workflows/repro.sh
- uses: actions/attest-build-provenance@v2
- uses: actions/attest-build-provenance@v3
if: matrix.os == 'ubuntu-latest'
with:
subject-path: |

View File

@@ -30,8 +30,8 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Format
@@ -98,27 +98,27 @@ jobs:
matrix:
os:
- name: freebsd
version: '14.2'
version: '14.3'
flags: '-test.v'
- name: netbsd
version: '10.1'
flags: '-test.v'
- name: freebsd
arch: arm64
version: '14.2'
version: '14.3'
flags: '-test.v -test.short'
- name: netbsd
arch: arm64
version: '10.1'
flags: '-test.v -test.short'
- name: openbsd
version: '7.6'
version: '7.8'
flags: '-test.v -test.short'
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Build
env:
@@ -128,7 +128,7 @@ jobs:
run: .github/workflows/build-test.sh
- name: Test
uses: cross-platform-actions/action@v0.27.0
uses: cross-platform-actions/action@v0.30.0
with:
operating_system: ${{ matrix.os.name }}
architecture: ${{ matrix.os.arch }}
@@ -155,7 +155,7 @@ jobs:
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Build
env:
@@ -174,8 +174,8 @@ jobs:
steps:
- uses: bytecodealliance/actions/wasmtime/setup@v1
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Set path
@@ -195,8 +195,8 @@ jobs:
steps:
- uses: docker/setup-qemu-action@v3
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test 386 (32-bit)
@@ -216,20 +216,20 @@ jobs:
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test
run: go test -v ./...
test-macintel:
runs-on: macos-13
runs-on: macos-15-intel
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test

View File

@@ -30,10 +30,10 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
wraps the [C SQLite API](https://sqlite.org/cintro.html)
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
embeds a build of SQLite into your application.
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
@@ -44,12 +44,19 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
### Advanced features
- [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio#example-package))
- [nested transactions](https://sqlite.org/lang_savepoint.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-Savepoint))
- [custom functions](https://sqlite.org/c3ref/create_function.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-Conn.CreateFunction))
- [virtual tables](https://sqlite.org/vtab.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-CreateModule))
- [custom VFSes](https://sqlite.org/vfs.html)
([examples](vfs/README.md#custom-vfses))
- [online backup](https://sqlite.org/backup.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#Conn))
- [JSON support](https://sqlite.org/json1.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package-Json))
- [math functions](https://sqlite.org/lang_mathfunc.html)
- [full-text search](https://sqlite.org/fts5.html)
- [geospatial search](https://sqlite.org/geopoly.html)
@@ -57,7 +64,6 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
- [statistics functions](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
- [encryption at rest](vfs/adiantum/README.md)
- [many extensions](ext/README.md)
- [custom VFSes](vfs/README.md#custom-vfses)
- [and more…](embed/README.md)
### Caveats
@@ -77,10 +83,19 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach)
thorough testing.
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) on
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (arm64/amd64),
Windows (amd64), FreeBSD (amd64/arm64), OpenBSD (amd64), NetBSD (amd64/arm64),
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).
Every commit is tested on:
* Linux: amd64, arm64, 386, riscv64, ppc64le, s390x
* macOS: amd64, arm64
* Windows: amd64
* BSD:
* FreeBSD: amd64, arm64
* OpenBSD: amd64
* NetBSD: amd64, arm64
* DragonFly BSD: amd64
* illumos: amd64
* Solaris: amd64
Certain operating system and CPU combinations have some limitations. See the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) for a complete overview.
The Go VFS is tested by running SQLite's
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c).
@@ -118,4 +133,4 @@ and features we're working on, planning to work on, or asking for help with.
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)

View File

@@ -109,7 +109,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
default:
return nil, MISUSE
case FCNTL_RESET_CACHE:
case FCNTL_RESET_CACHE, FCNTL_NULL_IO:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), 0))
@@ -265,7 +265,7 @@ func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pAr
}
}
if arg1 != nil {
_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
_ = c.trace(evt, arg1, arg2)
}
}
return rc

39
conn.go
View File

@@ -420,21 +420,21 @@ func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (
// Status retrieves runtime status information about a database connection.
//
// https://sqlite.org/c3ref/db_status.html
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err error) {
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int64, err error) {
defer c.arena.mark()()
hiPtr := c.arena.new(intlen)
curPtr := c.arena.new(intlen)
hiPtr := c.arena.new(8)
curPtr := c.arena.new(8)
var i int32
if reset {
i = 1
}
rc := res_t(c.call("sqlite3_db_status", stk_t(c.handle),
rc := res_t(c.call("sqlite3_db_status64", stk_t(c.handle),
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
if err = c.error(rc); err == nil {
current = int(util.Read32[int32](c.mod, curPtr))
highwater = int(util.Read32[int32](c.mod, hiPtr))
current = util.Read64[int64](c.mod, curPtr)
highwater = util.Read64[int64](c.mod, hiPtr)
}
return
}
@@ -444,20 +444,27 @@ func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err erro
// https://sqlite.org/c3ref/table_column_metadata.html
func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, collSeq string, notNull, primaryKey, autoInc bool, err error) {
defer c.arena.mark()()
var schemaPtr, columnPtr ptr_t
declTypePtr := c.arena.new(ptrlen)
collSeqPtr := c.arena.new(ptrlen)
notNullPtr := c.arena.new(ptrlen)
autoIncPtr := c.arena.new(ptrlen)
primaryKeyPtr := c.arena.new(ptrlen)
var (
declTypePtr ptr_t
collSeqPtr ptr_t
notNullPtr ptr_t
primaryKeyPtr ptr_t
autoIncPtr ptr_t
columnPtr ptr_t
schemaPtr ptr_t
)
if column != "" {
declTypePtr = c.arena.new(ptrlen)
collSeqPtr = c.arena.new(ptrlen)
notNullPtr = c.arena.new(ptrlen)
primaryKeyPtr = c.arena.new(ptrlen)
autoIncPtr = c.arena.new(ptrlen)
columnPtr = c.arena.string(column)
}
if schema != "" {
schemaPtr = c.arena.string(schema)
}
tablePtr := c.arena.string(table)
if column != "" {
columnPtr = c.arena.string(column)
}
rc := res_t(c.call("sqlite3_table_column_metadata", stk_t(c.handle),
stk_t(schemaPtr), stk_t(tablePtr), stk_t(columnPtr),

View File

@@ -73,6 +73,9 @@ const (
ERROR_MISSING_COLLSEQ ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8)
ERROR_RETRY ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8)
ERROR_SNAPSHOT ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8)
ERROR_RESERVESIZE ExtendedErrorCode = xErrorCode(ERROR) | (4 << 8)
ERROR_KEY ExtendedErrorCode = xErrorCode(ERROR) | (5 << 8)
ERROR_UNABLE ExtendedErrorCode = xErrorCode(ERROR) | (6 << 8)
IOERR_READ ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8)
IOERR_SHORT_READ ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8)
IOERR_WRITE ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8)
@@ -107,6 +110,8 @@ const (
IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8)
IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8)
IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8)
IOERR_BADKEY ExtendedErrorCode = xErrorCode(IOERR) | (35 << 8)
IOERR_CODEC ExtendedErrorCode = xErrorCode(IOERR) | (36 << 8)
LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8)
LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8)
BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8)
@@ -185,12 +190,12 @@ const (
type FunctionFlag uint32
const (
DETERMINISTIC FunctionFlag = 0x000000800
DIRECTONLY FunctionFlag = 0x000080000
INNOCUOUS FunctionFlag = 0x000200000
SELFORDER1 FunctionFlag = 0x002000000
// SUBTYPE FunctionFlag = 0x000100000
// RESULT_SUBTYPE FunctionFlag = 0x001000000
DETERMINISTIC FunctionFlag = 0x000000800
DIRECTONLY FunctionFlag = 0x000080000
SUBTYPE FunctionFlag = 0x000100000
INNOCUOUS FunctionFlag = 0x000200000
RESULT_SUBTYPE FunctionFlag = 0x001000000
SELFORDER1 FunctionFlag = 0x002000000
)
// StmtStatus name counter values associated with the [Stmt.Status] method.
@@ -229,7 +234,8 @@ const (
DBSTATUS_DEFERRED_FKS DBStatus = 10
DBSTATUS_CACHE_USED_SHARED DBStatus = 11
DBSTATUS_CACHE_SPILL DBStatus = 12
// DBSTATUS_MAX DBStatus = 12
DBSTATUS_TEMPBUF_SPILL DBStatus = 13
// DBSTATUS_MAX DBStatus = 13
)
// DBConfig are the available database connection configuration options.
@@ -280,6 +286,7 @@ const (
FCNTL_DATA_VERSION FcntlOpcode = 35
FCNTL_RESERVE_BYTES FcntlOpcode = 38
FCNTL_RESET_CACHE FcntlOpcode = 42
FCNTL_NULL_IO FcntlOpcode = 43
)
// LimitCategory are the available run-time limit categories.
@@ -361,13 +368,14 @@ const (
// CheckpointMode are all the checkpoint mode values.
//
// https://sqlite.org/c3ref/c_checkpoint_full.html
type CheckpointMode uint32
type CheckpointMode int32
const (
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
CHECKPOINT_NOOP CheckpointMode = -1 /* Do no work at all */
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
)
// TxnState are the allowed return values from [Conn.TxnState].

View File

@@ -1,7 +1,6 @@
package sqlite3
import (
"encoding/json"
"errors"
"math"
"time"
@@ -173,18 +172,6 @@ func (ctx Context) ResultPointer(ptr any) {
stk_t(ctx.handle), stk_t(valPtr))
}
// ResultJSON sets the result of the function to the JSON encoding of value.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultJSON(value any) {
data, err := json.Marshal(value)
if err != nil {
ctx.ResultError(err)
return // notest
}
ctx.ResultRawText(data)
}
// ResultValue sets the result of the function to a copy of [Value].
//
// https://sqlite.org/c3ref/result_blob.html
@@ -211,19 +198,27 @@ func (ctx Context) ResultError(err error) {
return
}
msg, code := errorCode(err, _OK)
msg, code := errorCode(err, ERROR)
if msg != "" {
defer ctx.c.arena.mark()()
ptr := ctx.c.arena.string(msg)
ctx.c.call("sqlite3_result_error",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(msg)))
}
if code != _OK {
if code != res_t(ERROR) {
ctx.c.call("sqlite3_result_error_code",
stk_t(ctx.handle), stk_t(code))
}
}
// ResultSubtype sets the subtype of the result of the function.
//
// https://sqlite.org/c3ref/result_subtype.html
func (ctx Context) ResultSubtype(t uint) {
ctx.c.call("sqlite3_result_subtype",
stk_t(ctx.handle), stk_t(uint32(t)))
}
// VTabNoChange may return true if a column is being fetched as part
// of an update during which the column value will not change.
//

View File

@@ -442,6 +442,22 @@ func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
return nil
}
// Deprecated: for Litestream use only; may be removed at any time.
func (c *conn) FileControlPersistWAL(schema string, mode int) (int, error) {
// notest
arg := make([]any, 1)
if mode >= 0 {
arg[0] = mode > 0
} else {
arg = arg[:0]
}
res, err := c.Conn.FileControl(schema, sqlite3.FCNTL_PERSIST_WAL, arg...)
if res == true {
return 1, err
}
return 0, err
}
type stmt struct {
*sqlite3.Stmt
tmWrite sqlite3.TimeFormat
@@ -546,8 +562,8 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
err = s.Stmt.BindTime(id, a, s.tmWrite)
case util.JSON:
err = s.Stmt.BindJSON(id, a.Value)
case util.PointerUnwrap:
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
case util.Pointer:
err = s.Stmt.BindPointer(id, a.Value)
case nil:
err = s.Stmt.BindNull(id)
default:
@@ -565,7 +581,7 @@ func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
switch arg.Value.(type) {
case bool, int, int64, float64, string, []byte,
time.Time, sqlite3.ZeroBlob,
util.JSON, util.PointerUnwrap,
util.JSON, util.Pointer,
nil:
return nil
default:
@@ -604,26 +620,27 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
return int64(r), nil
}
type rows struct {
ctx context.Context
*stmt
names []string
types []string
nulls []bool
scans []scantype
}
type scantype byte
const (
_ANY scantype = iota
_INT scantype = scantype(sqlite3.INTEGER)
_REAL scantype = scantype(sqlite3.FLOAT)
_TEXT scantype = scantype(sqlite3.TEXT)
_BLOB scantype = scantype(sqlite3.BLOB)
_NULL scantype = scantype(sqlite3.NULL)
_BOOL scantype = iota
_ANY scantype = iota
_INT
_REAL
_TEXT
_BLOB
_NULL
_BOOL
_TIME
_NOT_NULL
)
var (
_ [0]struct{} = [scantype(sqlite3.INTEGER) - _INT]struct{}{}
_ [0]struct{} = [scantype(sqlite3.FLOAT) - _REAL]struct{}{}
_ [0]struct{} = [scantype(sqlite3.TEXT) - _TEXT]struct{}{}
_ [0]struct{} = [scantype(sqlite3.BLOB) - _BLOB]struct{}{}
_ [0]struct{} = [scantype(sqlite3.NULL) - _NULL]struct{}{}
_ [0]struct{} = [_NOT_NULL & (_NOT_NULL - 1)]struct{}{}
)
func scanFromDecl(decl string) scantype {
@@ -648,10 +665,20 @@ func scanFromDecl(decl string) scantype {
return _ANY
}
type rows struct {
ctx context.Context
*stmt
names []string
types []string
scans []scantype
dest []driver.Value
}
var (
// Ensure these interfaces are implemented:
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
_ driver.RowsColumnTypeNullable = &rows{}
// _ driver.RowsColumnScanner = &rows{}
)
func (r *rows) Close() error {
@@ -674,34 +701,36 @@ func (r *rows) Columns() []string {
func (r *rows) scanType(index int) scantype {
if r.scans == nil {
count := r.Stmt.ColumnCount()
count := len(r.names)
scans := make([]scantype, count)
for i := range scans {
scans[i] = scanFromDecl(strings.ToUpper(r.Stmt.ColumnDeclType(i)))
}
r.scans = scans
}
return r.scans[index]
return r.scans[index] &^ _NOT_NULL
}
func (r *rows) loadColumnMetadata() {
if r.nulls == nil {
if r.types == nil {
c := r.Stmt.Conn()
count := r.Stmt.ColumnCount()
nulls := make([]bool, count)
count := len(r.names)
types := make([]string, count)
scans := make([]scantype, count)
for i := range nulls {
for i := range types {
var notnull bool
if col := r.Stmt.ColumnOriginName(i); col != "" {
types[i], _, nulls[i], _, _, _ = c.TableColumnMetadata(
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
r.Stmt.ColumnDatabaseName(i),
r.Stmt.ColumnTableName(i),
col)
types[i] = strings.ToUpper(types[i])
scans[i] = scanFromDecl(types[i])
if notnull {
scans[i] |= _NOT_NULL
}
}
}
r.nulls = nulls
r.types = types
r.scans = scans
}
@@ -720,15 +749,13 @@ func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
r.loadColumnMetadata()
if r.nulls[index] {
return false, true
}
return true, false
nullable = r.scans[index]&^_NOT_NULL == 0
return nullable, !nullable
}
func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
r.loadColumnMetadata()
scan := r.scans[index]
scan := r.scans[index] &^ _NOT_NULL
if r.Stmt.Busy() {
// SQLite is dynamically typed and we now have a row.
@@ -740,7 +767,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
switch {
case scan == _TIME && val != _BLOB && val != _NULL:
t := r.Stmt.ColumnTime(index, r.tmRead)
useValType = t == time.Time{}
useValType = t.IsZero()
case scan == _BOOL && val == _INT:
i := r.Stmt.ColumnInt64(index)
useValType = i != 0 && i != 1
@@ -771,6 +798,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
}
func (r *rows) Next(dest []driver.Value) error {
r.dest = nil
c := r.Stmt.Conn()
if old := c.SetInterrupt(r.ctx); old != r.ctx {
defer c.SetInterrupt(old)
@@ -789,18 +817,7 @@ func (r *rows) Next(dest []driver.Value) error {
}
for i := range dest {
scan := r.scanType(i)
switch v := dest[i].(type) {
case int64:
if scan == _BOOL {
switch v {
case 1:
dest[i] = true
case 0:
dest[i] = false
}
continue
}
case []byte:
if v, ok := dest[i].([]byte); ok {
if len(v) == cap(v) { // a BLOB
continue
}
@@ -815,18 +832,49 @@ func (r *rows) Next(dest []driver.Value) error {
}
}
dest[i] = string(v)
case float64:
break
default:
continue
}
if scan == _TIME {
switch scan {
case _TIME:
t, err := r.tmRead.Decode(dest[i])
if err == nil {
dest[i] = t
continue
}
case _BOOL:
switch dest[i] {
case int64(0):
dest[i] = false
case int64(1):
dest[i] = true
}
}
}
r.dest = dest
return nil
}
func (r *rows) ScanColumn(dest any, index int) (err error) {
// notest // Go 1.26
var tm *time.Time
var ok *bool
switch d := dest.(type) {
case *time.Time:
tm = d
case *sql.NullTime:
tm = &d.Time
ok = &d.Valid
case *sql.Null[time.Time]:
tm = &d.V
ok = &d.Valid
default:
return driver.ErrSkip
}
value := r.dest[index]
*tm, err = r.tmRead.Decode(value)
if ok != nil {
*ok = err == nil
if value == nil {
return nil
}
}
return err
}

View File

@@ -8,6 +8,7 @@ import (
"math"
"net/url"
"reflect"
"strings"
"testing"
"time"
@@ -33,7 +34,7 @@ func Test_Open_error(t *testing.T) {
func Test_Open_dir(t *testing.T) {
t.Parallel()
db, err := sql.Open("sqlite3", ".")
db, err := Open(".")
if err != nil {
t.Fatal(err)
}
@@ -43,18 +44,18 @@ func Test_Open_dir(t *testing.T) {
if err == nil {
t.Fatal("want error")
}
if !errors.Is(err, sqlite3.CANTOPEN) {
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) {
t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err)
}
}
func Test_Open_pragma(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_pragma": {"busy_timeout(1000)"},
})
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -72,11 +73,11 @@ func Test_Open_pragma(t *testing.T) {
func Test_Open_pragma_invalid(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_pragma": {"busy_timeout 1000"},
})
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -100,12 +101,12 @@ func Test_Open_pragma_invalid(t *testing.T) {
func Test_Open_txLock(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_txlock": {"exclusive"},
"_pragma": {"busy_timeout(1000)"},
})
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -136,11 +137,11 @@ func Test_Open_txLock(t *testing.T) {
func Test_Open_txLock_invalid(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_txlock": {"xclusive"},
})
_, err := sql.Open("sqlite3", tmp+"_txlock=xclusive")
_, err := Open(dsn)
if err == nil {
t.Fatal("want error")
}
@@ -151,31 +152,28 @@ func Test_Open_txLock_invalid(t *testing.T) {
func Test_BeginTx(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_txlock": {"exclusive"},
"_pragma": {"busy_timeout(0)"},
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
_, err = db.BeginTx(t.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(util.IsolationErr) {
t.Error("want isolationErr")
}
tx1, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
tx1, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
tx2, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
tx2, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
@@ -201,9 +199,9 @@ func Test_BeginTx(t *testing.T) {
func Test_nested_context(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -236,7 +234,7 @@ func Test_nested_context(t *testing.T) {
want(outer, 0)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
inner, err := tx.QueryContext(ctx, `SELECT value FROM generate_series(0)`)
@@ -259,9 +257,9 @@ func Test_nested_context(t *testing.T) {
func Test_Prepare(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -300,24 +298,21 @@ func Test_Prepare(t *testing.T) {
func Test_QueryRow_named(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(ctx)
conn, err := db.Conn(t.Context())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
stmt, err := conn.PrepareContext(ctx, `SELECT ?, ?5, :AAA, @AAA, $AAA`)
stmt, err := conn.PrepareContext(t.Context(), `SELECT ?, ?5, :AAA, @AAA, $AAA`)
if err != nil {
t.Fatal(err)
}
@@ -353,9 +348,9 @@ func Test_QueryRow_named(t *testing.T) {
func Test_QueryRow_blob_null(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -390,11 +385,11 @@ func Test_time(t *testing.T) {
for _, fmt := range []string{"auto", "sqlite", "rfc3339", time.ANSIC} {
t.Run(fmt, func(t *testing.T) {
tmp := memdb.TestDB(t, url.Values{
dsn := memdb.TestDB(t, url.Values{
"_timefmt": {fmt},
})
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -437,9 +432,9 @@ func Test_ColumnType_ScanType(t *testing.T) {
)
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := sql.Open("sqlite3", tmp)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
@@ -526,6 +521,39 @@ func Test_ColumnType_ScanType(t *testing.T) {
}
}
func Test_rows_ScanColumn(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var tm time.Time
err = db.QueryRow(`SELECT NULL`).Scan(&tm)
if err == nil {
t.Error("want error")
}
// Go 1.26
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
var nt sql.NullTime
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
if err != nil {
t.Error(err)
}
// Go 1.26
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
}
func Benchmark_loop(b *testing.B) {
db, err := Open(":memory:")
if err != nil {
@@ -539,12 +567,8 @@ func Benchmark_loop(b *testing.B) {
b.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
b.Cleanup(cancel)
b.ResetTimer()
for range b.N {
_, err := db.ExecContext(ctx,
for b.Loop() {
_, err := db.ExecContext(b.Context(),
`WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x < 1000000) SELECT x FROM c;`)
if err != nil {
b.Fatal(err)

View File

@@ -1,7 +1,6 @@
package driver
import (
"context"
"database/sql/driver"
"slices"
"testing"
@@ -56,7 +55,7 @@ func Fuzz_notWhitespace(f *testing.F) {
t.SkipNow()
}
c, err := db.Conn(context.Background())
c, err := db.Conn(t.Context())
if err != nil {
t.Fatal(err)
}

View File

@@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.49.1 for use with
This folder includes an embeddable Wasm build of SQLite 3.50.4 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:

Binary file not shown.

View File

@@ -53,7 +53,7 @@ func Test_bcw2(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if version != "3.50.0" {
if version != "3.51.0" {
t.Error(version)
}
}

View File

@@ -7,16 +7,18 @@ ROOT=../../
BINARYEN="$ROOT/tools/binaryen/bin"
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
trap 'rm -rf build/ sqlite/ bcw2.tmp' EXIT
trap 'rm -rf sqlite/ build/ bcw2.tmp' EXIT
mkdir -p sqlite/
mkdir -p build/ext/
cp "$ROOT"/sqlite3/*.[ch] build/
cp "$ROOT"/sqlite3/*.patch build/
cd sqlite/
# https://sqlite.org/src/info/3215186aa9204149
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=3215186a | tar xz
# https://sqlite.org/src/info/0e862bc9ed7aa9ae
curl -#L https://github.com/sqlite/sqlite/archive/0b99392.tar.gz | tar xz --strip-components=1
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=0e862bc9ed | tar xz --strip-components=1
cd sqlite
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
else
@@ -43,13 +45,14 @@ cd ~-
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o bcw2.wasm "build/main.c" \
-I"build" \
-o bcw2.wasm build/main.c \
-I"$ROOT/sqlite3/libc" -I"build" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-fno-stack-protector -fno-stack-clash-protection \
-mmutable-globals -mnontrapping-fptoint \
-msimd128 -mbulk-memory -msign-ext \
-mreference-types -mmultivalue \
-mno-extended-const \
-fno-stack-protector \
-Wl,--stack-first \
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
@@ -59,8 +62,9 @@ cd ~-
$(awk '{print "-Wl,--export="$0}' ../exports.txt)
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
bcw2.tmp -o bcw2.wasm --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext
"$BINARYEN/wasm-opt" -g bcw2.tmp -o bcw2.wasm \
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--strip --strip-producers

View File

@@ -1,14 +1,12 @@
module github.com/ncruces/go-sqlite3/embed/bcw2
go 1.23.0
go 1.24.0
toolchain go1.24.0
require github.com/ncruces/go-sqlite3 v0.25.0
require github.com/ncruces/go-sqlite3 v0.30.0
require (
github.com/ncruces/julianday v1.0.0 // indirect
github.com/ncruces/sort v0.1.5 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
golang.org/x/sys v0.32.0 // indirect
github.com/ncruces/sort v0.1.6 // indirect
github.com/tetratelabs/wazero v1.10.0 // indirect
golang.org/x/sys v0.38.0 // indirect
)

View File

@@ -1,12 +1,12 @@
github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
github.com/ncruces/go-sqlite3 v0.30.0 h1:EcoP5wtm8lRfRGQmOI782t/matUvzpqI3om9rTDngQ8=
github.com/ncruces/go-sqlite3 v0.30.0/go.mod h1:6mcRKUuA9qvAo7PcQU+wwcT2j/qGimg1qXO/pmj8Ryk=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=
github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/tetratelabs/wazero v1.10.0 h1:CXP3zneLDl6J4Zy8N/J+d5JsWKfrjE6GtvVK1fpnDlk=
github.com/tetratelabs/wazero v1.10.0/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=

View File

@@ -14,10 +14,11 @@ trap 'rm -f sqlite3.tmp' EXIT
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-fno-stack-protector -fno-stack-clash-protection \
-mmutable-globals -mnontrapping-fptoint \
-msimd128 -mbulk-memory -msign-ext \
-mreference-types -mmultivalue \
-mno-extended-const \
-fno-stack-protector \
-Wl,--stack-first \
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
@@ -26,8 +27,9 @@ trap 'rm -f sqlite3.tmp' EXIT
$(awk '{print "-Wl,--export="$0}' exports.txt)
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
sqlite3.tmp -o sqlite3.wasm --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext
"$BINARYEN/wasm-opt" -g sqlite3.tmp -o sqlite3.wasm \
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--strip --strip-producers

View File

@@ -59,7 +59,7 @@ sqlite3_db_filename
sqlite3_db_name
sqlite3_db_readonly
sqlite3_db_release_memory
sqlite3_db_status
sqlite3_db_status64
sqlite3_declare_vtab
sqlite3_errcode
sqlite3_errmsg
@@ -98,6 +98,7 @@ sqlite3_result_error_toobig
sqlite3_result_int64
sqlite3_result_null
sqlite3_result_pointer_go
sqlite3_result_subtype
sqlite3_result_text_go
sqlite3_result_value
sqlite3_result_zeroblob64
@@ -126,6 +127,7 @@ sqlite3_value_int64
sqlite3_value_nochange
sqlite3_value_numeric_type
sqlite3_value_pointer_go
sqlite3_value_subtype
sqlite3_value_text
sqlite3_value_type
sqlite3_vtab_collation

View File

@@ -17,5 +17,7 @@ import (
var binary string
func init() {
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
if sqlite3.Binary == nil {
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
}
}

View File

@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if version != "3.49.1" {
if version != "3.51.0" {
t.Error(version)
}
}

Binary file not shown.

View File

@@ -11,6 +11,7 @@ import (
//
// https://sqlite.org/c3ref/errcode.html
type Error struct {
sys error
msg string
sql string
code res_t
@@ -33,16 +34,28 @@ func (e *Error) ExtendedCode() ExtendedErrorCode {
// Error implements the error interface.
func (e *Error) Error() string {
var b strings.Builder
b.WriteString(util.ErrorCodeString(uint32(e.code)))
b.WriteString(util.ErrorCodeString(e.code))
if e.msg != "" {
b.WriteString(": ")
b.WriteString(e.msg)
}
if e.sys != nil {
b.WriteString(": ")
b.WriteString(e.sys.Error())
}
return b.String()
}
// Unwrap returns the underlying operating system error
// that caused the I/O error or failure to open a file.
//
// https://sqlite.org/c3ref/system_errno.html
func (e *Error) Unwrap() error {
return e.sys
}
// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode].
//
// It makes it possible to do:
@@ -90,7 +103,16 @@ func (e *Error) SQL() string {
// Error implements the error interface.
func (e ErrorCode) Error() string {
return util.ErrorCodeString(uint32(e))
return util.ErrorCodeString(e)
}
// As converts this error to an [ExtendedErrorCode].
func (e ErrorCode) As(err any) bool {
c, ok := err.(*xErrorCode)
if ok {
*c = xErrorCode(e)
}
return ok
}
// Temporary returns true for [BUSY] errors.
@@ -105,7 +127,7 @@ func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
// Error implements the error interface.
func (e ExtendedErrorCode) Error() string {
return util.ErrorCodeString(uint32(e))
return util.ErrorCodeString(e)
}
// Is tests whether this error matches a given [ErrorCode].
@@ -150,14 +172,10 @@ func errorCode(err error, def ErrorCode) (msg string, code res_t) {
return code.msg, res_t(code.code)
}
var ecode ErrorCode
var xcode xErrorCode
switch {
case errors.As(err, &xcode):
if errors.As(err, &xcode) {
code = res_t(xcode)
case errors.As(err, &ecode):
code = res_t(ecode)
default:
} else {
code = res_t(def)
}
return err.Error(), code

View File

@@ -30,7 +30,7 @@ you can load into your database connections.
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab).
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
provides [statistics](https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
- [`github.com/ncruces/go-sqlite3/ext/uuid`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/uuid)
@@ -38,11 +38,11 @@ you can load into your database connections.
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
maps multidimensional data to one dimension.
### Pakages
### Packages
These packages may also be useful to work with SQLite:
- [`github.com/ncruces/decimal`](https://pkg.go.dev/github.com/ncruces/decimal)
decimal arithmetic.
- [`github.com/ncruces/julianday`](https://pkg.go.dev/github.com/ncruces/julianday)
Julian day math.
Julian day math.

View File

@@ -59,7 +59,8 @@ func (c *cursor) Next() error {
}
func (c *cursor) RowID() (int64, error) {
return int64(c.rowID), nil
// One-based RowID for consistency with carray and other tables.
return int64(c.rowID) + 1, nil
}
func (c *cursor) Column(ctx sqlite3.Context, n int) error {

View File

@@ -88,9 +88,9 @@ func Example() {
func Test_cursor_Column(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, array.Register)
db, err := driver.Open(dsn, array.Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -1,6 +1,7 @@
package blobio_test
import (
"database/sql"
"io"
"log"
"os"
@@ -34,7 +35,8 @@ func Example() {
const message = "Hello BLOB!"
// Create the BLOB.
r, err := db.Exec(`INSERT INTO test VALUES (?)`, sqlite3.ZeroBlob(len(message)))
r, err := db.Exec(`INSERT INTO test VALUES (:data)`,
sql.Named("data", sqlite3.ZeroBlob(len(message))))
if err != nil {
log.Fatal(err)
}
@@ -45,15 +47,19 @@ func Example() {
}
// Write the BLOB.
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', ?, 0, ?)`,
id, message)
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', :rowid, :offset, :message)`,
sql.Named("rowid", id),
sql.Named("offset", 0),
sql.Named("message", message))
if err != nil {
log.Fatal(err)
}
// Read the BLOB.
_, err = db.Exec(`SELECT readblob('main', 'test', 'col', ?, 0, ?)`,
id, sqlite3.Pointer(os.Stdout))
_, err = db.Exec(`SELECT readblob('main', 'test', 'col', :rowid, :offset, :writer)`,
sql.Named("rowid", id),
sql.Named("offset", 0),
sql.Named("writer", sqlite3.Pointer(os.Stdout)))
if err != nil {
log.Fatal(err)
}
@@ -138,18 +144,16 @@ func Test_readblob(t *testing.T) {
}
}
if stmt.Step() {
got := stmt.ColumnText(0)
if got != tt.want1 {
t.Errorf("got %q", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != tt.want1 {
t.Errorf("got %q", got)
}
if stmt.Step() {
got := stmt.ColumnText(0)
if got != tt.want2 {
t.Errorf("got %q", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != tt.want2 {
t.Errorf("got %q", got)
}
err = stmt.Err()

View File

@@ -154,6 +154,7 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
return sqlite3.CONSTRAINT
}
idx.IdxFlags = sqlite3.INDEX_SCAN_HEX
idx.EstimatedCost = cost
idx.IdxNum = plan
return nil

View File

@@ -147,20 +147,21 @@ func TestAffinity(t *testing.T) {
}
defer stmt.Close()
if stmt.Step() {
if got := stmt.ColumnText(0); got != "1" {
t.Errorf("got %q want 1", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != "1" {
t.Errorf("got %q want 1", got)
}
if stmt.Step() {
if got := stmt.ColumnText(0); got != "0.1" {
t.Errorf("got %q want 0.1", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != "0.1" {
t.Errorf("got %q want 0.1", got)
}
if stmt.Step() {
if got := stmt.ColumnText(0); got != "e" {
t.Errorf("got %q want e", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != "e" {
t.Errorf("got %q want e", got)
}
}

View File

@@ -18,7 +18,7 @@ func Register(db *sqlite3.Conn) error {
return RegisterFS(db, nil)
}
// Register registers SQL functions readfile, lsmode,
// RegisterFS registers SQL functions readfile, lsmode,
// and the table-valued function fsdir;
// fsys will be used to read files and list directories.
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {

View File

@@ -17,9 +17,9 @@ import (
func Test_lsmode(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, fileio.Register)
db, err := driver.Open(dsn, fileio.Register)
if err != nil {
t.Fatal(err)
}
@@ -53,9 +53,9 @@ func Test_readfile(t *testing.T) {
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
t.Run("", func(t *testing.T) {
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
fileio.RegisterFS(c, fsys)
return nil
})

View File

@@ -21,9 +21,9 @@ func Test_fsdir(t *testing.T) {
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
t.Run("", func(t *testing.T) {
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
fileio.RegisterFS(c, fsys)
return nil
})

View File

@@ -15,9 +15,9 @@ import (
func Test_writefile(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -4,6 +4,7 @@ import (
_ "crypto/md5"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha3"
_ "crypto/sha512"
"testing"
@@ -11,7 +12,6 @@ import (
_ "golang.org/x/crypto/blake2s"
_ "golang.org/x/crypto/md4"
_ "golang.org/x/crypto/ripemd160"
_ "golang.org/x/crypto/sha3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
@@ -21,7 +21,7 @@ import (
func TestRegister(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
tests := []struct {
name string
@@ -55,7 +55,7 @@ func TestRegister(t *testing.T) {
{"blake2b('', 256)", "0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8"},
}
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -12,9 +12,9 @@ import (
func TestRegister(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, ipaddr.Register)
db, err := driver.Open(dsn, ipaddr.Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -67,9 +67,9 @@ func Example() {
func Test_lines(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, lines.Register)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}
@@ -98,9 +98,9 @@ func Test_lines(t *testing.T) {
func Test_lines_error(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, lines.Register)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}
@@ -123,9 +123,9 @@ func Test_lines_error(t *testing.T) {
func Test_lines_read(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, lines.Register)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}
@@ -155,9 +155,9 @@ func Test_lines_read(t *testing.T) {
func Test_lines_test(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, lines.Register)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}

View File

@@ -141,10 +141,10 @@ func TestRegister(t *testing.T) {
}
defer stmt.Close()
if stmt.Step() {
if got := stmt.ColumnInt(0); got != 3 {
t.Errorf("got %d, want 3", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnInt(0); got != 3 {
t.Errorf("got %d, want 3", got)
}
err = db.Exec(`ALTER TABLE v_x RENAME TO v_y`)

View File

@@ -15,9 +15,9 @@ import (
func TestRegister(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}
@@ -38,7 +38,7 @@ func TestRegister(t *testing.T) {
{`regexp_instr('Hello', '.', 6)`, ""},
{`regexp_substr('Hello', 'el.')`, "ell"},
{`regexp_replace('Hello', 'llo', 'll')`, "Hell"},
// https://www.postgresql.org/docs/current/functions-matching.html
// https://postgresql.org/docs/current/functions-matching.html
{`regexp_count('ABCABCAXYaxy', 'A.')`, "3"},
{`regexp_count('ABCABCAXYaxy', '(?i)A.', 1)`, "4"},
{`regexp_instr('number of your street, town zip, FR', '[^,]+', 1, 2)`, "23"},
@@ -80,9 +80,9 @@ func TestRegister(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}
@@ -107,9 +107,9 @@ func TestRegister_errors(t *testing.T) {
func TestRegister_pointer(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -2,9 +2,8 @@
package serdes
import (
"io"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/util/vfsutil"
"github.com/ncruces/go-sqlite3/vfs"
)
@@ -14,16 +13,16 @@ func init() {
vfs.Register(vfsName, sliceVFS{})
}
var fileToOpen = make(chan *sliceFile, 1)
var fileToOpen = make(chan *[]byte, 1)
// Serialize backs up a database into a byte slice.
//
// https://sqlite.org/c3ref/serialize.html
func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
var file sliceFile
var file []byte
fileToOpen <- &file
err := db.Backup(schema, "file:serdes.db?vfs="+vfsName)
return file.data, err
err := db.Backup(schema, "file:serdes.db?nolock=1&vfs="+vfsName)
return file, err
}
// Deserialize restores a database from a byte slice,
@@ -41,8 +40,8 @@ func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
// ["memdb"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb
// ["reader"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs
func Deserialize(db *sqlite3.Conn, schema string, data []byte) error {
fileToOpen <- &sliceFile{data}
return db.Restore(schema, "file:serdes.db?vfs="+vfsName)
fileToOpen <- &data
return db.Restore(schema, "file:serdes.db?immutable=1&vfs="+vfsName)
}
type sliceVFS struct{}
@@ -53,14 +52,14 @@ func (sliceVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, e
}
select {
case file := <-fileToOpen:
return file, flags | vfs.OPEN_MEMORY, nil
return (*vfsutil.SliceFile)(file), flags | vfs.OPEN_MEMORY, nil
default:
return nil, flags, sqlite3.MISUSE
}
}
func (sliceVFS) Delete(name string, dirSync bool) error {
// notest // OPEN_MEMORY
// notest // no journals to delete
return sqlite3.IOERR_DELETE
}
@@ -71,70 +70,3 @@ func (sliceVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
func (sliceVFS) FullPathname(name string) (string, error) {
return name, nil
}
type sliceFile struct{ data []byte }
func (f *sliceFile) ReadAt(b []byte, off int64) (n int, err error) {
if d := f.data; off < int64(len(d)) {
n = copy(b, d[off:])
}
if n == 0 {
err = io.EOF
}
return
}
func (f *sliceFile) WriteAt(b []byte, off int64) (n int, err error) {
if d := f.data; off > int64(len(d)) {
f.data = append(d, make([]byte, off-int64(len(d)))...)
}
d := append(f.data[:off], b...)
if len(d) > len(f.data) {
f.data = d
}
return len(b), nil
}
func (f *sliceFile) Size() (int64, error) {
return int64(len(f.data)), nil
}
func (f *sliceFile) Truncate(size int64) error {
if d := f.data; size < int64(len(d)) {
f.data = d[:size]
}
return nil
}
func (f *sliceFile) SizeHint(size int64) error {
if d := f.data; size > int64(len(d)) {
f.data = append(d, make([]byte, size-int64(len(d)))...)
}
return nil
}
func (*sliceFile) Close() error { return nil }
func (*sliceFile) Sync(flag vfs.SyncFlag) error { return nil }
func (*sliceFile) Lock(lock vfs.LockLevel) error { return nil }
func (*sliceFile) Unlock(lock vfs.LockLevel) error { return nil }
func (*sliceFile) CheckReservedLock() (bool, error) {
// notest // OPEN_MEMORY
return false, nil
}
func (*sliceFile) SectorSize() int {
// notest // IOCAP_POWERSAFE_OVERWRITE
return 0
}
func (*sliceFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
return vfs.IOCAP_ATOMIC |
vfs.IOCAP_SAFE_APPEND |
vfs.IOCAP_SEQUENTIAL |
vfs.IOCAP_POWERSAFE_OVERWRITE |
vfs.IOCAP_SUBPAGE_READ
}

View File

@@ -1,6 +1,7 @@
package serdes_test
import (
_ "embed"
"errors"
"io"
"net/http"
@@ -11,7 +12,30 @@ import (
"github.com/ncruces/go-sqlite3/ext/serdes"
)
func TestDeserialize(t *testing.T) {
//go:embed testdata/wal.db
var walDB []byte
func Test_wal(t *testing.T) {
db, err := sqlite3.Open("testdata/wal.db")
if err != nil {
t.Fatal(err)
}
defer db.Close()
data, err := serdes.Serialize(db, "main")
if err != nil {
t.Fatal(err)
}
compareDBs(t, data, walDB)
err = serdes.Deserialize(db, "temp", walDB)
if err != nil {
t.Fatal(err)
}
}
func Test_northwind(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
@@ -37,10 +61,14 @@ func TestDeserialize(t *testing.T) {
t.Fatal(err)
}
if len(input) != len(output) {
compareDBs(t, input, output)
}
func compareDBs(t *testing.T, a, b []byte) {
if len(a) != len(b) {
t.Fatal("lengths are different")
}
for i := range input {
for i := range a {
// These may be different.
switch {
case 24 <= i && i < 28:
@@ -53,14 +81,14 @@ func TestDeserialize(t *testing.T) {
// SQLite version that wrote the file.
continue
}
if input[i] != output[i] {
t.Errorf("difference at %d: %d %d", i, input[i], output[i])
if a[i] != b[i] {
t.Errorf("difference at %d: %d %d", i, a[i], b[i])
}
}
}
func httpGet() ([]byte, error) {
res, err := http.Get("https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/refs/heads/main/dist/northwind.db")
res, err := http.Get("https://github.com/jpwhite3/northwind-SQLite3/raw/refs/heads/main/dist/northwind.db")
if err != nil {
return nil, err
}

BIN
ext/serdes/testdata/wal.db vendored Normal file

Binary file not shown.

View File

@@ -92,7 +92,9 @@ func TestRegister(t *testing.T) {
}
defer stmt.Close()
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
x := stmt.ColumnInt(0)
y := stmt.ColumnInt(1)
hypot := stmt.ColumnInt(2)

View File

@@ -1,6 +1,6 @@
# ANSI SQL Aggregate Functions
https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
## Built in aggregates

View File

@@ -37,7 +37,9 @@ func TestRegister_boolean(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnBool(0); got != true {
t.Errorf("got %v, want true", got)
}

View File

@@ -19,8 +19,8 @@ type mode struct {
func (m mode) Value(ctx sqlite3.Context) {
var (
max = 0
typ = sqlite3.NULL
max uint
i64 int64
f64 float64
str string
@@ -32,7 +32,6 @@ func (m mode) Value(ctx sqlite3.Context) {
i64 = k
}
}
f64 = float64(i64)
for k, v := range m.reals {
if v > max || v == max && k < f64 {
typ = sqlite3.FLOAT
@@ -66,33 +65,45 @@ func (m mode) Value(ctx sqlite3.Context) {
}
}
func (b *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (m *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
switch arg[0].Type() {
case sqlite3.INTEGER:
b.ints.add(arg[0].Int64())
if m.reals == nil {
m.ints.add(arg[0].Int64())
break
}
fallthrough
case sqlite3.FLOAT:
b.reals.add(arg[0].Float())
m.reals.add(arg[0].Float())
for k, v := range m.ints {
m.reals[float64(k)] += v
}
m.ints = nil
case sqlite3.TEXT:
b.texts.add(arg[0].Text())
m.texts.add(arg[0].Text())
case sqlite3.BLOB:
b.blobs.add(string(arg[0].RawBlob()))
m.blobs.add(string(arg[0].RawBlob()))
}
}
func (b *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (m *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
switch arg[0].Type() {
case sqlite3.INTEGER:
b.ints.del(arg[0].Int64())
if m.reals == nil {
m.ints.del(arg[0].Int64())
break
}
fallthrough
case sqlite3.FLOAT:
b.reals.del(arg[0].Float())
m.reals.del(arg[0].Float())
case sqlite3.TEXT:
b.texts.del(arg[0].Text())
m.texts.del(arg[0].Text())
case sqlite3.BLOB:
b.blobs.del(string(arg[0].RawBlob()))
m.blobs.del(string(arg[0].RawBlob()))
}
}
type counter[T comparable] map[T]int
type counter[T comparable] map[T]uint
func (c *counter[T]) add(k T) {
if (*c) == nil {
@@ -102,11 +113,9 @@ func (c *counter[T]) add(k T) {
}
func (c counter[T]) del(k T) {
switch n := c[k]; n {
default:
c[k] = n - 1
case 1:
if n := c[k]; n == 1 {
delete(c, k)
case 0:
} else {
c[k] = n - 1
}
}

View File

@@ -21,10 +21,10 @@ func TestRegister_mode(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnInt(0); got != 3 {
t.Errorf("got %v, want 3", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnInt(0); got != 3 {
t.Errorf("got %v, want 3", got)
}
stmt.Close()
@@ -32,10 +32,10 @@ func TestRegister_mode(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnInt(0); got != 1 {
t.Errorf("got %v, want 1", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnInt(0); got != 1 {
t.Errorf("got %v, want 1", got)
}
stmt.Close()
@@ -43,10 +43,10 @@ func TestRegister_mode(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 2.5 {
t.Errorf("got %v, want 2.5", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 2.5 {
t.Errorf("got %v, want 2.5", got)
}
stmt.Close()
@@ -54,21 +54,22 @@ func TestRegister_mode(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnText(0); got != "red" {
t.Errorf("got %q, want red", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != "red" {
t.Errorf("got %q, want red", got)
}
stmt.Close()
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (X'cafebabe'), ('green'), ('blue'), (X'cafebabe'))`)
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
t.Errorf("got %q, want cafebabe", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
t.Errorf("got %q, want cafebabe", got)
}
stmt.Close()
@@ -82,4 +83,20 @@ func TestRegister_mode(t *testing.T) {
for stmt.Step() {
}
stmt.Close()
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (?), (?), (?), (?), (?))`)
if err != nil {
t.Fatal(err)
}
stmt.BindInt(1, 1)
stmt.BindInt(2, 1)
stmt.BindInt(3, 2)
stmt.BindFloat(4, 2)
stmt.BindFloat(5, 2)
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnInt(0); got != 2 {
t.Errorf("got %v, want 2", got)
}
stmt.Close()
}

View File

@@ -38,7 +38,9 @@ func TestRegister_percentile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnFloat(0); got != 10 {
t.Errorf("got %v, want 10", got)
}
@@ -65,30 +67,30 @@ func TestRegister_percentile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 5.5 {
t.Errorf("got %v, want 5.5", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 5.5 {
t.Errorf("got %v, want 5.5", got)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 7 {
t.Errorf("got %v, want 7", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 7 {
t.Errorf("got %v, want 7", got)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 10 {
t.Errorf("got %v, want 10", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 10 {
t.Errorf("got %v, want 10", got)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 14.5 {
t.Errorf("got %v, want 14.5", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 14.5 {
t.Errorf("got %v, want 14.5", got)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 16 {
t.Errorf("got %v, want 16", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnFloat(0); got != 16 {
t.Errorf("got %v, want 16", got)
}
stmt.Close()
@@ -103,7 +105,9 @@ func TestRegister_percentile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnFloat(0); got != 4 {
t.Errorf("got %v, want 4", got)
}
@@ -134,7 +138,9 @@ func TestRegister_percentile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnType(0); got != sqlite3.NULL {
t.Error("want NULL")
}

View File

@@ -47,7 +47,7 @@
//
// [Built-in Aggregate Functions]: https://sqlite.org/lang_aggfunc.html
// [Built-in Window Functions]: https://sqlite.org/windowfunctions.html#builtins
// [ANSI SQL Aggregate Functions]: https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
// [ANSI SQL Aggregate Functions]: https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
package stats
import (
@@ -58,8 +58,11 @@ import (
// Register registers statistics functions.
func Register(db *sqlite3.Conn) error {
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
const order = sqlite3.SELFORDER1 | flags
const (
flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
json = sqlite3.RESULT_SUBTYPE | flags
order = sqlite3.SELFORDER1 | flags
)
return errors.Join(
db.CreateWindowFunction("var_pop", 1, flags, newVariance(var_pop)),
db.CreateWindowFunction("var_samp", 1, flags, newVariance(var_samp)),
@@ -81,7 +84,7 @@ func Register(db *sqlite3.Conn) error {
db.CreateWindowFunction("regr_slope", 2, flags, newCovariance(regr_slope)),
db.CreateWindowFunction("regr_intercept", 2, flags, newCovariance(regr_intercept)),
db.CreateWindowFunction("regr_count", 2, flags, newCovariance(regr_count)),
db.CreateWindowFunction("regr_json", 2, flags, newCovariance(regr_json)),
db.CreateWindowFunction("regr_json", 2, json, newCovariance(regr_json)),
db.CreateWindowFunction("median", 1, order, newPercentile(median)),
db.CreateWindowFunction("percentile", 2, order, newPercentile(percentile_100)),
db.CreateWindowFunction("percentile_cont", 2, order, newPercentile(percentile_cont)),
@@ -227,6 +230,7 @@ func (fn *covariance) Value(ctx sqlite3.Context) {
case regr_json:
var buf [128]byte
ctx.ResultRawText(fn.regr_json(buf[:0]))
ctx.ResultSubtype('J')
return
}
ctx.ResultFloat(r)

View File

@@ -34,10 +34,10 @@ func TestRegister_variance(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnType(0); got != sqlite3.NULL {
t.Errorf("got %v, want NULL", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else if got := stmt.ColumnType(0); got != sqlite3.NULL {
t.Errorf("got %v, want NULL", got)
}
stmt.Close()
@@ -57,7 +57,9 @@ func TestRegister_variance(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnFloat(0); got != 40 {
t.Errorf("got %v, want 40", got)
}
@@ -131,7 +133,9 @@ func TestRegister_covariance(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnInt(0); got != 0 {
t.Errorf("got %v, want 0", got)
}
@@ -156,49 +160,50 @@ func TestRegister_covariance(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if stmt.Step() {
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
t.Errorf("got %v, want 0.9881049293224639", got)
}
if got := stmt.ColumnFloat(1); got != 21.25 {
t.Errorf("got %v, want 21.25", got)
}
if got := stmt.ColumnFloat(2); got != 17 {
t.Errorf("got %v, want 17", got)
}
if got := stmt.ColumnFloat(3); got != 4.2 {
t.Errorf("got %v, want 4.2", got)
}
if got := stmt.ColumnFloat(4); got != 75 {
t.Errorf("got %v, want 75", got)
}
if got := stmt.ColumnFloat(5); got != 14.8 {
t.Errorf("got %v, want 14.8", got)
}
if got := stmt.ColumnFloat(6); got != 500 {
t.Errorf("got %v, want 500", got)
}
if got := stmt.ColumnFloat(7); got != 85 {
t.Errorf("got %v, want 85", got)
}
if got := stmt.ColumnFloat(8); got != 0.17 {
t.Errorf("got %v, want 0.17", got)
}
if got := stmt.ColumnFloat(9); got != -8.55 {
t.Errorf("got %v, want -8.55", got)
}
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
t.Errorf("got %v, want 0.9763513513513513", got)
}
if got := stmt.ColumnInt(11); got != 5 {
t.Errorf("got %v, want 5", got)
}
var a map[string]float64
if err := stmt.ColumnJSON(12, &a); err != nil {
t.Error(err)
} else if got := a["count"]; got != 5 {
t.Errorf("got %v, want 5", got)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
}
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
t.Errorf("got %v, want 0.9881049293224639", got)
}
if got := stmt.ColumnFloat(1); got != 21.25 {
t.Errorf("got %v, want 21.25", got)
}
if got := stmt.ColumnFloat(2); got != 17 {
t.Errorf("got %v, want 17", got)
}
if got := stmt.ColumnFloat(3); got != 4.2 {
t.Errorf("got %v, want 4.2", got)
}
if got := stmt.ColumnFloat(4); got != 75 {
t.Errorf("got %v, want 75", got)
}
if got := stmt.ColumnFloat(5); got != 14.8 {
t.Errorf("got %v, want 14.8", got)
}
if got := stmt.ColumnFloat(6); got != 500 {
t.Errorf("got %v, want 500", got)
}
if got := stmt.ColumnFloat(7); got != 85 {
t.Errorf("got %v, want 85", got)
}
if got := stmt.ColumnFloat(8); got != 0.17 {
t.Errorf("got %v, want 0.17", got)
}
if got := stmt.ColumnFloat(9); got != -8.55 {
t.Errorf("got %v, want -8.55", got)
}
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
t.Errorf("got %v, want 0.9763513513513513", got)
}
if got := stmt.ColumnInt(11); got != 5 {
t.Errorf("got %v, want 5", got)
}
var a map[string]float64
if err := stmt.ColumnJSON(12, &a); err != nil {
t.Error(err)
} else if got := a["count"]; got != 5 {
t.Errorf("got %v, want 5", got)
}
stmt.Close()
@@ -248,7 +253,9 @@ func Benchmark_average(b *testing.B) {
b.Fatal(err)
}
if stmt.Step() {
if !stmt.Step() {
b.Fatal(stmt.Err())
} else {
want := float64(b.N) / 2
if got := stmt.ColumnFloat(0); got != want {
b.Errorf("got %v, want %v", got, want)
@@ -282,7 +289,9 @@ func Benchmark_variance(b *testing.B) {
b.Fatal(err)
}
if stmt.Step() && b.N > 100 {
if !stmt.Step() {
b.Fatal(stmt.Err())
} else if b.N > 100 {
want := float64(b.N*b.N) / 12
if got := stmt.ColumnFloat(0); want > (got-want)*float64(b.N) {
b.Errorf("got %v, want %v", got, want)

View File

@@ -43,7 +43,7 @@ import (
"github.com/ncruces/go-sqlite3/internal/util"
)
// Set RegisterLike to false to not register a Unicode aware LIKE operator.
// RegisterLike must be set to false to not register a Unicode aware LIKE operator.
// Overriding the built-in LIKE operator disables the [LIKE optimization].
//
// [LIKE optimization]: https://sqlite.org/optoverview.html#the_like_optimization

View File

@@ -26,11 +26,10 @@ func TestRegister(t *testing.T) {
}
defer stmt.Close()
if stmt.Step() {
return stmt.ColumnText(0)
if !stmt.Step() {
t.Fatal(stmt.Err())
}
t.Fatal(stmt.Err())
return ""
return stmt.ColumnText(0)
}
Register(db)

View File

@@ -194,7 +194,7 @@ func timestamp(ctx sqlite3.Context, arg ...sqlite3.Value) {
switch u.Version() {
case 1, 2, 6, 7:
ctx.ResultTime(
time.Unix(u.Time().UnixTime()),
time.Unix(u.Time().UnixTime()).UTC(),
sqlite3.TimeFormatDefault)
}
}

View File

@@ -14,9 +14,9 @@ import (
func Test_generate(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}
@@ -153,9 +153,9 @@ func Test_generate(t *testing.T) {
func Test_convert(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, Register)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
}

View File

@@ -19,9 +19,9 @@ func Register(db *sqlite3.Conn) error {
}
func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
var x [63]int64
if len(arg) > len(x) {
ctx.ResultError(util.ErrorString("zorder: too many parameters"))
var x [24]int64
if n := len(arg); n < 2 || n > 24 {
ctx.ResultError(util.ErrorString("zorder: needs between 2 and 24 dimensions"))
return
}
for i := range arg {
@@ -29,17 +29,15 @@ func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
}
var z int64
if len(arg) > 0 {
for i := range x {
j := i % len(arg)
z |= (x[j] & 1) << i
x[j] >>= 1
}
for i := range 63 {
j := i % len(arg)
z |= (x[j] & 1) << i
x[j] >>= 1
}
for i := range arg {
if x[i] != 0 {
ctx.ResultError(util.ErrorString("zorder: parameter too large"))
ctx.ResultError(util.ErrorString("zorder: argument out of range"))
return
}
}
@@ -51,6 +49,19 @@ func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
n := arg[1].Int64()
z := arg[0].Int64()
if n < 2 || n > 24 {
ctx.ResultError(util.ErrorString("unzorder: needs between 2 and 24 dimensions"))
return
}
if i < 0 || i >= n {
ctx.ResultError(util.ErrorString("unzorder: index out of range"))
return
}
if z < 0 {
ctx.ResultError(util.ErrorString("unzorder: argument out of range"))
return
}
var k int
var x int64
for j := i; j < 63; j += n {

View File

@@ -12,11 +12,11 @@ import (
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
func TestRegister_zorder(t *testing.T) {
func Test_zorder(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, zorder.Register)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
}
@@ -57,11 +57,11 @@ func TestRegister_zorder(t *testing.T) {
}
}
func TestRegister_unzorder(t *testing.T) {
func Test_unzorder(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, zorder.Register)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
}
@@ -85,11 +85,11 @@ func TestRegister_unzorder(t *testing.T) {
}
}
func TestRegister_error(t *testing.T) {
func Test_zorder_error(t *testing.T) {
t.Parallel()
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
db, err := driver.Open(tmp, zorder.Register)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
}
@@ -103,7 +103,7 @@ func TestRegister_error(t *testing.T) {
var buf strings.Builder
buf.WriteString("SELECT zorder(0")
for i := 1; i < 80; i++ {
for i := 1; i < 25; i++ {
buf.WriteByte(',')
buf.WriteString(strconv.Itoa(0))
}
@@ -113,3 +113,30 @@ func TestRegister_error(t *testing.T) {
t.Error("want error")
}
}
func Test_unzorder_error(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var got int64
err = db.QueryRow(`SELECT unzorder(-1, 2, 0)`).Scan(&got)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT unzorder(0, 2, 2)`).Scan(&got)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT unzorder(0, 25, 2)`).Scan(&got)
if err == nil {
t.Error("want error")
}
}

14
func.go
View File

@@ -59,7 +59,7 @@ func (c *Conn) CreateCollation(name string, fn CollatingFunction) error {
return c.error(rc)
}
// Collating function is the type of a collation callback.
// CollatingFunction is the type of a collation callback.
// Implementations must not retain a or b.
type CollatingFunction func(a, b []byte) int
@@ -132,7 +132,7 @@ func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn
if win, ok := agg.(WindowFunction); ok {
return win
}
return windowFunc{agg, name}
return agg
}))
}
rc := res_t(c.call("sqlite3_create_window_function_go",
@@ -307,13 +307,3 @@ func (a *aggregateFunc) Close() error {
a.stop()
return nil
}
type windowFunc struct {
AggregateFunction
name string
}
func (w windowFunc) Inverse(ctx Context, arg ...Value) {
// Implementing inverse allows certain queries that don't really need it to succeed.
ctx.ResultError(util.ErrorString(w.name + ": may not be used as a window function"))
}

17
go.mod
View File

@@ -1,23 +1,22 @@
module github.com/ncruces/go-sqlite3
go 1.23.0
toolchain go1.24.0
go 1.24.0
require (
github.com/ncruces/julianday v1.0.0
github.com/ncruces/sort v0.1.5
github.com/tetratelabs/wazero v1.9.0
golang.org/x/crypto v0.37.0
golang.org/x/sys v0.32.0
github.com/ncruces/sort v0.1.6
github.com/ncruces/wbt v0.2.0
github.com/tetratelabs/wazero v1.10.0
golang.org/x/sys v0.38.0
)
require (
github.com/dchest/siphash v1.2.3 // ext/bloom
github.com/google/uuid v1.6.0 // ext/uuid
github.com/psanford/httpreadat v0.1.0 // example
golang.org/x/sync v0.13.0 // test
golang.org/x/text v0.24.0 // ext/unicode
golang.org/x/crypto v0.43.0 // vfs/adiantum vfs/xts
golang.org/x/sync v0.17.0 // test
golang.org/x/text v0.30.0 // ext/unicode
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
)

26
go.sum
View File

@@ -4,19 +4,21 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=
github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
github.com/tetratelabs/wazero v1.10.0 h1:CXP3zneLDl6J4Zy8N/J+d5JsWKfrjE6GtvVK1fpnDlk=
github.com/tetratelabs/wazero v1.10.0/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=

View File

@@ -209,8 +209,12 @@ func (d *ddl) renameTable(dst, src string) error {
return nil
}
func compileConstraintRegexp(name string) *regexp.Regexp {
return regexp.MustCompile("^(?i:CONSTRAINT)\\s+[\"`]?" + regexp.QuoteMeta(name) + "[\"`\\s]")
}
func (d *ddl) addConstraint(name string, sql string) {
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
reg := compileConstraintRegexp(name)
for i := 0; i < len(d.fields); i++ {
if reg.MatchString(d.fields[i]) {
@@ -223,7 +227,7 @@ func (d *ddl) addConstraint(name string, sql string) {
}
func (d *ddl) removeConstraint(name string) bool {
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
reg := compileConstraintRegexp(name)
for i := 0; i < len(d.fields); i++ {
if reg.MatchString(d.fields[i]) {
@@ -236,7 +240,7 @@ func (d *ddl) removeConstraint(name string) bool {
//lint:ignore U1000 ignore unused code.
func (d *ddl) hasConstraint(name string) bool {
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
reg := compileConstraintRegexp(name)
for _, f := range d.fields {
if reg.MatchString(f) {

View File

@@ -95,7 +95,7 @@ func parseAllColumns(in string) ([]string, error) {
}
return nil, fmt.Errorf("unexpected token: %s", string(s[i]))
case parseAllColumnsState_State_End:
break
continue // avoid SA4011
}
}
if state != parseAllColumnsState_State_End {

View File

@@ -313,6 +313,41 @@ func TestRemoveConstraint(t *testing.T) {
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "lowercase",
fields: []string{"`id` integer NOT NULL", "constraint `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
cName: "fk_users_notes",
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "mixed_case",
fields: []string{"`id` integer NOT NULL", "cOnsTraiNT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
cName: "fk_users_notes",
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "newline",
fields: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes`\nFOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
cName: "fk_users_notes",
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "lots_of_newlines",
fields: []string{"`id` integer NOT NULL", "constraint \n fk_users_notes \n FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
cName: "fk_users_notes",
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "no_backtick",
fields: []string{"`id` integer NOT NULL", "CONSTRAINT fk_users_notes FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
cName: "fk_users_notes",
success: true,
expect: []string{"`id` integer NOT NULL"},
},
{
name: "check",
fields: []string{"CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')", "`id` integer NOT NULL"},

View File

@@ -1,19 +1,17 @@
module github.com/ncruces/go-sqlite3/gormlite
go 1.23.0
toolchain go1.24.0
go 1.24.0
require (
github.com/ncruces/go-sqlite3 v0.25.0
gorm.io/gorm v1.25.12
github.com/ncruces/go-sqlite3 v0.30.0
gorm.io/gorm v1.31.1
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
github.com/tetratelabs/wazero v1.10.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.30.0 // indirect
)

View File

@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
github.com/ncruces/go-sqlite3 v0.30.0 h1:EcoP5wtm8lRfRGQmOI782t/matUvzpqI3om9rTDngQ8=
github.com/ncruces/go-sqlite3 v0.30.0/go.mod h1:6mcRKUuA9qvAo7PcQU+wwcT2j/qGimg1qXO/pmj8Ryk=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
github.com/tetratelabs/wazero v1.10.0 h1:CXP3zneLDl6J4Zy8N/J+d5JsWKfrjE6GtvVK1fpnDlk=
github.com/tetratelabs/wazero v1.10.0/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@@ -339,7 +339,7 @@ func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
indexes := make([]gorm.Index, 0)
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
rst := make([]*_Index, 0)
if err := m.DB.Debug().Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
if err := m.DB.Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
return err
}
for _, index := range rst {

View File

@@ -1,4 +1,3 @@
// Package gormlite provides a GORM driver for SQLite.
package gormlite
import (
@@ -52,7 +51,9 @@ func (dialector _Dialector) Initialize(db *gorm.DB) (err error) {
})
for k, v := range dialector.ClauseBuilders() {
db.ClauseBuilders[k] = v
if _, ok := db.ClauseBuilders[k]; !ok {
db.ClauseBuilders[k] = v
}
}
return
}

View File

@@ -14,10 +14,10 @@ import (
)
func TestDialector(t *testing.T) {
tmp := memdb.TestDB(t)
dsn := memdb.TestDB(t)
// Custom connection with a custom function called "my_custom_function".
db, err := driver.Open(tmp, func(conn *sqlite3.Conn) error {
db, err := driver.Open(dsn, func(conn *sqlite3.Conn) error {
return conn.CreateFunction("my_custom_function", 0, sqlite3.DETERMINISTIC,
func(ctx sqlite3.Context, arg ...sqlite3.Value) {
ctx.ResultText("my-result")
@@ -36,14 +36,14 @@ func TestDialector(t *testing.T) {
}{
{
description: "Default driver",
dialector: Open(tmp),
dialector: Open(dsn),
openSuccess: true,
query: "SELECT 1",
querySuccess: true,
},
{
description: "Custom function",
dialector: Open(tmp),
dialector: Open(dsn),
openSuccess: true,
query: "SELECT my_custom_function()",
querySuccess: false,

View File

@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
go work use -r .
go test
git clone --branch v1.25.12 --filter=blob:none https://github.com/go-gorm/gorm.git
git clone --branch v1.31.1 --filter=blob:none https://github.com/go-gorm/gorm.git
mv gorm/tests tests
rm -rf gorm/

View File

@@ -7,13 +7,14 @@ diff --git a/tests/.gitignore b/tests/.gitignore
diff --git a/tests/tests_test.go b/tests/tests_test.go
--- a/tests/tests_test.go
+++ b/tests/tests_test.go
@@ -7,9 +7,11 @@ import (
@@ -8,9 +8,11 @@ import (
"path/filepath"
"time"
+ _ "github.com/ncruces/go-sqlite3/embed"
+ sqlite "github.com/ncruces/go-sqlite3/gormlite"
+
"gorm.io/driver/gaussdb"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
- "gorm.io/driver/sqlite"

View File

@@ -3,13 +3,12 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/error_translator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/migrator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_parse_all_columns.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_parse_all_columns_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/error_translator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/migrator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/sqlite.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/sqlite_test.go"
curl -#L "https://github.com/glebarez/sqlite/raw/v1.11.0/sqlite_error_translator_test.go" > error_translator_test.go

View File

@@ -9,24 +9,31 @@ import (
"golang.org/x/sys/unix"
)
func NewMemory(_, max uint64) experimental.LinearMemory {
func NewMemory(cap, max uint64) experimental.LinearMemory {
// Round up to the page size.
rnd := uint64(unix.Getpagesize() - 1)
max = (max + rnd) &^ rnd
res := (max + rnd) &^ rnd
if max > math.MaxInt {
// This ensures int(max) overflows to a negative value,
if res > math.MaxInt {
// This ensures int(res) overflows to a negative value,
// and unix.Mmap returns EINVAL.
max = math.MaxUint64
res = math.MaxUint64
}
// Reserve max bytes of address space, to ensure we won't need to move it.
com := res
prot := unix.PROT_READ | unix.PROT_WRITE
if cap < max { // Commit memory only if cap=max.
com = 0
prot = unix.PROT_NONE
}
// Reserve res bytes of address space, to ensure we won't need to move it.
// A protected, private, anonymous mapping should not commit memory.
b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON)
b, err := unix.Mmap(-1, 0, int(res), prot, unix.MAP_PRIVATE|unix.MAP_ANON)
if err != nil {
panic(err)
}
return &mmappedMemory{buf: b[:0]}
return &mmappedMemory{buf: b[:com]}
}
// The slice covers the entire mmapped memory:
@@ -40,9 +47,11 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte {
com := uint64(len(m.buf))
res := uint64(cap(m.buf))
if com < size && size <= res {
// Round up to the page size.
// Grow geometrically, round up to the page size.
rnd := uint64(unix.Getpagesize() - 1)
new := (size + rnd) &^ rnd
new := com + com>>3
new = min(max(size, new), res)
new = (new + rnd) &^ rnd
// Commit additional memory up to new bytes.
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
@@ -50,8 +59,7 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte {
return nil
}
// Update committed memory.
m.buf = m.buf[:new]
m.buf = m.buf[:new] // Update committed memory.
}
// Limit returned capacity because bytes beyond
// len(m.buf) have not yet been committed.

View File

@@ -9,20 +9,26 @@ import (
"golang.org/x/sys/windows"
)
func NewMemory(_, max uint64) experimental.LinearMemory {
func NewMemory(cap, max uint64) experimental.LinearMemory {
// Round up to the page size.
rnd := uint64(windows.Getpagesize() - 1)
max = (max + rnd) &^ rnd
res := (max + rnd) &^ rnd
if max > math.MaxInt {
// This ensures uintptr(max) overflows to a large value,
if res > math.MaxInt {
// This ensures uintptr(res) overflows to a large value,
// and windows.VirtualAlloc returns an error.
max = math.MaxUint64
res = math.MaxUint64
}
// Reserve max bytes of address space, to ensure we won't need to move it.
// This does not commit memory.
r, err := windows.VirtualAlloc(0, uintptr(max), windows.MEM_RESERVE, windows.PAGE_READWRITE)
com := res
kind := windows.MEM_COMMIT
if cap < max { // Commit memory only if cap=max.
com = 0
kind = windows.MEM_RESERVE
}
// Reserve res bytes of address space, to ensure we won't need to move it.
r, err := windows.VirtualAlloc(0, uintptr(res), uint32(kind), windows.PAGE_READWRITE)
if err != nil {
panic(err)
}
@@ -30,8 +36,9 @@ func NewMemory(_, max uint64) experimental.LinearMemory {
mem := virtualMemory{addr: r}
// SliceHeader, although deprecated, avoids a go vet warning.
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf))
sh.Cap = int(max)
sh.Data = r
sh.Len = int(com)
sh.Cap = int(res)
return &mem
}
@@ -47,9 +54,11 @@ func (m *virtualMemory) Reallocate(size uint64) []byte {
com := uint64(len(m.buf))
res := uint64(cap(m.buf))
if com < size && size <= res {
// Round up to the page size.
// Grow geometrically, round up to the page size.
rnd := uint64(windows.Getpagesize() - 1)
new := (size + rnd) &^ rnd
new := com + com>>3
new = min(max(size, new), res)
new = (new + rnd) &^ rnd
// Commit additional memory up to new bytes.
_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE)
@@ -57,8 +66,7 @@ func (m *virtualMemory) Reallocate(size uint64) []byte {
return nil
}
// Update committed memory.
m.buf = m.buf[:new]
m.buf = m.buf[:new] // Update committed memory.
}
// Limit returned capacity because bytes beyond
// len(m.buf) have not yet been committed.

View File

@@ -39,6 +39,9 @@ const (
ERROR_MISSING_COLLSEQ = ERROR | (1 << 8)
ERROR_RETRY = ERROR | (2 << 8)
ERROR_SNAPSHOT = ERROR | (3 << 8)
ERROR_RESERVESIZE = ERROR | (4 << 8)
ERROR_KEY = ERROR | (5 << 8)
ERROR_UNABLE = ERROR | (6 << 8)
IOERR_READ = IOERR | (1 << 8)
IOERR_SHORT_READ = IOERR | (2 << 8)
IOERR_WRITE = IOERR | (3 << 8)
@@ -73,6 +76,8 @@ const (
IOERR_DATA = IOERR | (32 << 8)
IOERR_CORRUPTFS = IOERR | (33 << 8)
IOERR_IN_PAGE = IOERR | (34 << 8)
IOERR_BADKEY = IOERR | (35 << 8)
IOERR_CODEC = IOERR | (36 << 8)
LOCKED_SHAREDCACHE = LOCKED | (1 << 8)
LOCKED_VTAB = LOCKED | (2 << 8)
BUSY_RECOVERY = BUSY | (1 << 8)

View File

@@ -33,8 +33,12 @@ func AssertErr() ErrorString {
return ErrorString(msg)
}
func ErrorCodeString(rc uint32) string {
switch rc {
type errorCode interface {
~uint8 | ~uint16 | ~uint32 | ~int32
}
func ErrorCodeString[T errorCode](rc T) string {
switch uint32(rc) {
case ABORT_ROLLBACK:
return "sqlite3: abort due to ROLLBACK"
case ROW:
@@ -42,7 +46,7 @@ func ErrorCodeString(rc uint32) string {
case DONE:
return "sqlite3: no more rows available"
}
switch rc & 0xff {
switch uint8(rc) {
case OK:
return "sqlite3: not an error"
case ERROR:

View File

@@ -20,20 +20,6 @@ func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(con
Export(name)
}
type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1)
func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[1] // prevent bounds check on every slice access
fn(ctx, mod, T0(stack[0]), T1(stack[1]))
}
func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(funcVII[T0, T1](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil).
Export(name)
}
type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {

View File

@@ -1,3 +1,5 @@
//go:build !goexperiment.jsonv2
package util
import (

52
internal/util/json_v2.go Normal file
View File

@@ -0,0 +1,52 @@
//go:build goexperiment.jsonv2
package util
import (
"encoding/json/v2"
"math"
"strconv"
"time"
"unsafe"
)
type JSON struct{ Value any }
func (j JSON) Scan(value any) error {
var buf []byte
switch v := value.(type) {
case []byte:
buf = v
case string:
buf = unsafe.Slice(unsafe.StringData(v), len(v))
case int64:
buf = strconv.AppendInt(nil, v, 10)
case float64:
buf = AppendNumber(nil, v)
case time.Time:
buf = append(buf, '"')
buf = v.AppendFormat(buf, time.RFC3339Nano)
buf = append(buf, '"')
case nil:
buf = []byte("null")
default:
panic(AssertErr())
}
return json.Unmarshal(buf, j.Value)
}
func AppendNumber(dst []byte, f float64) []byte {
switch {
case math.IsNaN(f):
dst = append(dst, "null"...)
case math.IsInf(f, 1):
dst = append(dst, "9.0e999"...)
case math.IsInf(f, -1):
dst = append(dst, "-9.0e999"...)
default:
return strconv.AppendFloat(dst, f, 'g', -1, 64)
}
return dst
}

View File

@@ -1,12 +1,10 @@
package util
import (
"context"
"os"
"reflect"
"unsafe"
"github.com/tetratelabs/wazero/api"
"golang.org/x/sys/windows"
)
@@ -16,14 +14,21 @@ type MappedRegion struct {
addr uintptr
}
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32) (*MappedRegion, error) {
h, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
func MapRegion(f *os.File, offset int64, size int32) (*MappedRegion, error) {
maxSize := offset + int64(size)
h, err := windows.CreateFileMapping(
windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE,
uint32(maxSize>>32), uint32(maxSize), nil)
if h == 0 {
return nil, err
}
const allocationGranularity = 64 * 1024
align := offset % allocationGranularity
offset -= align
a, err := windows.MapViewOfFile(h, windows.FILE_MAP_WRITE,
uint32(offset>>32), uint32(offset), uintptr(size))
uint32(offset>>32), uint32(offset), uintptr(size)+uintptr(align))
if a == 0 {
windows.CloseHandle(h)
return nil, err
@@ -32,9 +37,9 @@ func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, si
ret := &MappedRegion{Handle: h, addr: a}
// SliceHeader, although deprecated, avoids a go vet warning.
sh := (*reflect.SliceHeader)(unsafe.Pointer(&ret.Data))
sh.Data = a + uintptr(align)
sh.Len = int(size)
sh.Cap = int(size)
sh.Data = a
return ret, nil
}

View File

@@ -12,6 +12,7 @@ type ConnKey struct{}
type moduleKey struct{}
type moduleState struct {
sysError error
mmapState
handleState
}
@@ -23,3 +24,20 @@ func NewContext(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, moduleKey{}, state)
return ctx
}
func GetSystemError(ctx context.Context) error {
// Test needed to simplify testing.
s, ok := ctx.Value(moduleKey{}).(*moduleState)
if ok {
return s.sysError
}
return nil
}
func SetSystemError(ctx context.Context, err error) {
// Test needed to simplify testing.
s, ok := ctx.Value(moduleKey{}).(*moduleState)
if ok {
s.sysError = err
}
}

View File

@@ -1,11 +1,3 @@
package util
type Pointer[T any] struct{ Value T }
func (p Pointer[T]) unwrap() any { return p.Value }
type PointerUnwrap interface{ unwrap() any }
func UnwrapPointer(p PointerUnwrap) any {
return p.unwrap()
}
type Pointer struct{ Value any }

View File

@@ -1,13 +0,0 @@
package util
import (
"math"
"testing"
)
func TestUnwrapPointer(t *testing.T) {
p := Pointer[float64]{Value: math.Pi}
if got := UnwrapPointer(p); got != math.Pi {
t.Errorf("want π, got %v", got)
}
}

83
json.go
View File

@@ -1,6 +1,13 @@
//go:build !goexperiment.jsonv2
package sqlite3
import "github.com/ncruces/go-sqlite3/internal/util"
import (
"encoding/json"
"strconv"
"github.com/ncruces/go-sqlite3/internal/util"
)
// JSON returns a value that can be used as an argument to
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
@@ -10,3 +17,77 @@ import "github.com/ncruces/go-sqlite3/internal/util"
func JSON(value any) any {
return util.JSON{Value: value}
}
// ResultJSON sets the result of the function to the JSON encoding of value.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultJSON(value any) {
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
ctx.ResultRawText(p[:len(p)-1]) // remove the newline
return 0, nil
})).Encode(value)
if err != nil {
ctx.ResultError(err)
return // notest
}
}
// BindJSON binds the JSON encoding of value to the prepared statement.
// The leftmost SQL parameter has an index of 1.
//
// https://sqlite.org/c3ref/bind_blob.html
func (s *Stmt) BindJSON(param int, value any) error {
return json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
return 0, s.BindRawText(param, p[:len(p)-1]) // remove the newline
})).Encode(value)
}
// ColumnJSON parses the JSON-encoded value of the result column
// and stores it in the value pointed to by ptr.
// The leftmost column of the result set has the index 0.
//
// https://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ColumnJSON(col int, ptr any) error {
var data []byte
switch s.ColumnType(col) {
case NULL:
data = []byte("null")
case TEXT:
data = s.ColumnRawText(col)
case BLOB:
data = s.ColumnRawBlob(col)
case INTEGER:
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
case FLOAT:
data = util.AppendNumber(nil, s.ColumnFloat(col))
default:
panic(util.AssertErr())
}
return json.Unmarshal(data, ptr)
}
// JSON parses a JSON-encoded value
// and stores the result in the value pointed to by ptr.
func (v Value) JSON(ptr any) error {
var data []byte
switch v.Type() {
case NULL:
data = []byte("null")
case TEXT:
data = v.RawText()
case BLOB:
data = v.RawBlob()
case INTEGER:
data = strconv.AppendInt(nil, v.Int64(), 10)
case FLOAT:
data = util.AppendNumber(nil, v.Float())
default:
panic(util.AssertErr())
}
return json.Unmarshal(data, ptr)
}
type callbackWriter func(p []byte) (int, error)
func (fn callbackWriter) Write(p []byte) (int, error) { return fn(p) }

113
json_v2.go Normal file
View File

@@ -0,0 +1,113 @@
//go:build goexperiment.jsonv2
package sqlite3
import (
"encoding/json/v2"
"strconv"
"github.com/ncruces/go-sqlite3/internal/util"
)
// JSON returns a value that can be used as an argument to
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
// store value as JSON, or decode JSON into value.
// JSON should NOT be used with [Stmt.BindJSON], [Stmt.ColumnJSON],
// [Value.JSON], or [Context.ResultJSON].
func JSON(value any) any {
return util.JSON{Value: value}
}
// ResultJSON sets the result of the function to the JSON encoding of value.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultJSON(value any) {
w := bytesWriter{sqlite: ctx.c.sqlite}
if err := json.MarshalWrite(&w, value); err != nil {
ctx.c.free(w.ptr)
ctx.ResultError(err)
return // notest
}
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(w.ptr), stk_t(len(w.buf)))
}
// BindJSON binds the JSON encoding of value to the prepared statement.
// The leftmost SQL parameter has an index of 1.
//
// https://sqlite.org/c3ref/bind_blob.html
func (s *Stmt) BindJSON(param int, value any) error {
w := bytesWriter{sqlite: s.c.sqlite}
if err := json.MarshalWrite(&w, value); err != nil {
s.c.free(w.ptr)
return err
}
rc := res_t(s.c.call("sqlite3_bind_text_go",
stk_t(s.handle), stk_t(param),
stk_t(w.ptr), stk_t(len(w.buf))))
return s.c.error(rc)
}
// ColumnJSON parses the JSON-encoded value of the result column
// and stores it in the value pointed to by ptr.
// The leftmost column of the result set has the index 0.
//
// https://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ColumnJSON(col int, ptr any) error {
var data []byte
switch s.ColumnType(col) {
case NULL:
data = []byte("null")
case TEXT:
data = s.ColumnRawText(col)
case BLOB:
data = s.ColumnRawBlob(col)
case INTEGER:
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
case FLOAT:
data = util.AppendNumber(nil, s.ColumnFloat(col))
default:
panic(util.AssertErr())
}
return json.Unmarshal(data, ptr)
}
// JSON parses a JSON-encoded value
// and stores the result in the value pointed to by ptr.
func (v Value) JSON(ptr any) error {
var data []byte
switch v.Type() {
case NULL:
data = []byte("null")
case TEXT:
data = v.RawText()
case BLOB:
data = v.RawBlob()
case INTEGER:
data = strconv.AppendInt(nil, v.Int64(), 10)
case FLOAT:
data = util.AppendNumber(nil, v.Float())
default:
panic(util.AssertErr())
}
return json.Unmarshal(data, ptr)
}
type bytesWriter struct {
*sqlite
buf []byte
ptr ptr_t
}
func (b *bytesWriter) Write(p []byte) (n int, err error) {
if len(p) > cap(b.buf)-len(b.buf) {
want := int64(len(b.buf)) + int64(len(p))
grow := int64(cap(b.buf))
grow += grow >> 1
want = max(want, grow)
b.ptr = b.realloc(b.ptr, want)
b.buf = util.View(b.mod, b.ptr, want)[:len(b.buf)]
}
b.buf = append(b.buf, p...)
return len(p), nil
}

11
litestream/modernc/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module modernc.org/sqlite
go 1.24.0
require github.com/ncruces/go-sqlite3 v0.30.0
require (
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.10.0 // indirect
golang.org/x/sys v0.38.0 // indirect
)

10
litestream/modernc/go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/ncruces/go-sqlite3 v0.30.0 h1:EcoP5wtm8lRfRGQmOI782t/matUvzpqI3om9rTDngQ8=
github.com/ncruces/go-sqlite3 v0.30.0/go.mod h1:6mcRKUuA9qvAo7PcQU+wwcT2j/qGimg1qXO/pmj8Ryk=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.10.0 h1:CXP3zneLDl6J4Zy8N/J+d5JsWKfrjE6GtvVK1fpnDlk=
github.com/tetratelabs/wazero v1.10.0/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=

View File

@@ -0,0 +1,20 @@
// Package sqlite provides a shim that allows Litestream to work with the ncruces SQLite driver.
package sqlite
import (
"database/sql"
"slices"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
)
func init() {
if !slices.Contains(sql.Drivers(), "sqlite") {
sql.Register("sqlite", &driver.SQLite{})
}
}
type FileControl interface {
FileControlPersistWAL(string, int) (int, error)
}

View File

@@ -8,6 +8,6 @@ import "github.com/ncruces/go-sqlite3/internal/util"
// [Value.Pointer], or [Context.ResultPointer].
//
// https://sqlite.org/bindptr.html
func Pointer[T any](value T) any {
return util.Pointer[T]{Value: value}
func Pointer(value any) any {
return util.Pointer{Value: value}
}

View File

@@ -5,11 +5,14 @@ import (
"context"
"math/bits"
"os"
"runtime"
"strings"
"sync"
"unsafe"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
@@ -78,7 +81,9 @@ func compileSQLite() {
return
}
instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin)
instance.compiled, instance.err = instance.runtime.CompileModule(
experimental.WithCompilationWorkers(ctx, runtime.GOMAXPROCS(0)/4),
bin)
}
type sqlite struct {
@@ -124,14 +129,14 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error {
panic(util.OOMErr)
}
var msg, query string
if handle != 0 {
var msg, query string
if ptr := ptr_t(sqlt.call("sqlite3_errmsg", stk_t(handle))); ptr != 0 {
msg = util.ReadString(sqlt.mod, ptr, _MAX_LENGTH)
switch {
case msg == "not an error":
msg = ""
case msg == util.ErrorCodeString(uint32(rc))[len("sqlite3: "):]:
msg = strings.TrimPrefix(msg, "sqlite3: ")
msg = strings.TrimPrefix(msg, util.ErrorCodeString(rc)[len("sqlite3: "):])
msg = strings.TrimPrefix(msg, ": ")
if msg == "" || msg == "not an error" {
msg = ""
}
}
@@ -141,10 +146,16 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error {
query = sql[0][i:]
}
}
}
if msg != "" || query != "" {
return &Error{code: rc, msg: msg, sql: query}
}
var sys error
switch ErrorCode(rc) {
case CANTOPEN, IOERR:
sys = util.GetSystemError(sqlt.ctx)
}
if sys != nil || msg != "" || query != "" {
return &Error{code: rc, sys: sys, msg: msg, sql: query}
}
return xErrorCode(rc)
}

View File

@@ -2,7 +2,7 @@
# handle, and interrupt, sqlite3_busy_timeout.
--- sqlite3.c.orig
+++ sqlite3.c
@@ -183355,7 +183355,7 @@
@@ -186667,7 +186667,7 @@
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
if( ms>0 ){
@@ -10,4 +10,4 @@
+ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteBusyCallback,
(void*)db);
db->busyTimeout = ms;
}else{
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT

View File

@@ -3,46 +3,54 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3490100.zip"
unzip -d . sqlite-amalgamation-*.zip
mv sqlite-amalgamation-*/sqlite3.c .
mv sqlite-amalgamation-*/sqlite3.h .
mv sqlite-amalgamation-*/sqlite3ext.h .
rm -rf sqlite-amalgamation-*
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510000.tar.gz"
# To test a snapshot:
# Verify download.
if hash=$(openssl dgst -sha3-256 sqlite-autoconf-*.tar.gz); then
if ! [[ $hash =~ fa52f9cc74dbca004aa650ae698036a3350611f672649e165078f4eae21d6a2e ]]; then
echo $hash
exit 1
fi
fi 2> /dev/null
tar xzf sqlite-autoconf-*.tar.gz
# To test a snapshot instead:
# curl -# https://sqlite.org/snapshot/sqlite-snapshot-202410081727.tar.gz | tar xz
# mv sqlite-snapshot-*/sqlite3.c .
# mv sqlite-snapshot-*/sqlite3.h .
# mv sqlite-snapshot-*/sqlite3ext.h .
# rm -rf sqlite-snapshot-*
mv sqlite-*/sqlite3.c .
mv sqlite-*/sqlite3.h .
mv sqlite-*/sqlite3ext.h .
rm -r sqlite-*
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.0"
mkdir -p ext/
cd ext/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/ieee754.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/spellfix.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/uint.c"
curl -#OL "$GITHUB_TAG/ext/misc/anycollseq.c"
curl -#OL "$GITHUB_TAG/ext/misc/base64.c"
curl -#OL "$GITHUB_TAG/ext/misc/decimal.c"
curl -#OL "$GITHUB_TAG/ext/misc/ieee754.c"
curl -#OL "$GITHUB_TAG/ext/misc/regexp.c"
curl -#OL "$GITHUB_TAG/ext/misc/series.c"
curl -#OL "$GITHUB_TAG/ext/misc/spellfix.c"
curl -#OL "$GITHUB_TAG/ext/misc/uint.c"
cd ~-
cd ../vfs/tests/mptest/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/multiwrite01.test"
curl -#OL "$GITHUB_TAG/mptest/config01.test"
curl -#OL "$GITHUB_TAG/mptest/config02.test"
curl -#OL "$GITHUB_TAG/mptest/crash01.test"
curl -#OL "$GITHUB_TAG/mptest/crash02.subtest"
curl -#OL "$GITHUB_TAG/mptest/multiwrite01.test"
cd ~-
cd ../vfs/tests/mptest/wasm/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/mptest.c"
curl -#OL "$GITHUB_TAG/mptest/mptest.c"
cd ~-
cd ../vfs/tests/speedtest1/wasm/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/test/speedtest1.c"
curl -#OL "$GITHUB_TAG/test/speedtest1.c"
cd ~-
cat *.patch | patch -p0 --no-backup-if-mismatch
cat *.patch | patch -p0 --no-backup-if-mismatch

View File

@@ -11,40 +11,43 @@ SRCS="${1:-libc.c}"
trap 'rm -f libc.c libc.tmp' EXIT
cat << EOF > libc.c
#include <string.h>
#include <stdlib.h>
#include <string.h>
EOF
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o libc.wasm -I. "$SRCS" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-fno-stack-protector -fno-stack-clash-protection \
-Wl,-z,stack-size=1024 \
-mmutable-globals -mnontrapping-fptoint \
-msimd128 -mbulk-memory -msign-ext \
-mreference-types -mmultivalue \
-mno-extended-const \
-fno-stack-protector \
-Wl,-z,stack-size=4096 \
-Wl,--stack-first \
-Wl,--import-undefined \
-Wl,--initial-memory=16777216 \
-Wl,--export=memchr \
-Wl,--export=memcmp \
-Wl,--export=memcpy \
-Wl,--export=memmove \
-Wl,--export=memrchr \
-Wl,--export=memset \
-Wl,--export=strchr \
-Wl,--export=strchrnul \
-Wl,--export=strcmp \
-Wl,--export=strcspn \
-Wl,--export=strlen \
-Wl,--export=strncmp \
-Wl,--export=strrchr \
-Wl,--export=strspn \
-Wl,--export=qsort
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
libc.tmp -o libc.wasm \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext
"$BINARYEN/wasm-opt" -g libc.tmp -o libc.wasm \
--low-memory-unused --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--strip --strip-debug --strip-producers
"$BINARYEN/wasm-dis" -o libc.wat libc.wasm

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,12 @@
package libc
import (
"bytes"
"context"
_ "embed"
"math"
"os"
"strings"
"testing"
"github.com/tetratelabs/wazero"
@@ -29,9 +32,8 @@ var (
memcmp api.Function
strlen api.Function
strchr api.Function
strcmp api.Function
strspn api.Function
strncmp api.Function
strrchr api.Function
strcspn api.Function
stack [8]uint64
)
@@ -61,9 +63,8 @@ func TestMain(m *testing.M) {
memcmp = mod.ExportedFunction("memcmp")
strlen = mod.ExportedFunction("strlen")
strchr = mod.ExportedFunction("strchr")
strcmp = mod.ExportedFunction("strcmp")
strspn = mod.ExportedFunction("strspn")
strncmp = mod.ExportedFunction("strncmp")
strrchr = mod.ExportedFunction("strrchr")
strcspn = mod.ExportedFunction("strcspn")
memory, _ = mod.Memory().Read(0, mod.Memory().Size())
@@ -74,8 +75,7 @@ func Benchmark_memset(b *testing.B) {
clear(memory)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(memset, ptr1, 3, size)
}
}
@@ -85,24 +85,54 @@ func Benchmark_memcpy(b *testing.B) {
fill(memory[ptr2:ptr2+size], 5)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(memcpy, ptr1, ptr2, size)
}
}
func Benchmark_strlen(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 5)
b.SetBytes(size)
for b.Loop() {
call(strlen, ptr1)
}
}
func Benchmark_memchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(memchr, ptr1, 5, size)
}
}
func Benchmark_strchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size-1], 5)
b.SetBytes(size/2 + 1)
for b.Loop() {
call(strchr, ptr1, 5)
}
}
func Benchmark_strrchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 5)
fill(memory[ptr1+size/2:ptr1+size-1], 7)
b.SetBytes(size/2 + 1)
for b.Loop() {
call(strrchr, ptr1, 5)
}
}
func Benchmark_memcmp(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size], 7)
@@ -110,61 +140,11 @@ func Benchmark_memcmp(b *testing.B) {
fill(memory[ptr2+size/2:ptr2+size], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(memcmp, ptr1, ptr2, size)
}
}
func Benchmark_strlen(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 5)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(strlen, ptr1)
}
}
func Benchmark_strchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size-1], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(strchr, ptr1, 5)
}
}
func Benchmark_strcmp(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 7)
fill(memory[ptr2:ptr2+size/2], 7)
fill(memory[ptr2+size/2:ptr2+size-1], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(strcmp, ptr1, ptr2, size)
}
}
func Benchmark_strncmp(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 7)
fill(memory[ptr2:ptr2+size/2], 7)
fill(memory[ptr2+size/2:ptr2+size-1], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(strncmp, ptr1, ptr2, size-1)
}
}
func Benchmark_strspn(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
@@ -175,8 +155,7 @@ func Benchmark_strspn(b *testing.B) {
memory[ptr2+3] = 9
b.SetBytes(size)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(strspn, ptr1, ptr2)
}
}
@@ -189,59 +168,17 @@ func Benchmark_strcspn(b *testing.B) {
memory[ptr2+1] = 9
b.SetBytes(size)
b.ResetTimer()
for range b.N {
for b.Loop() {
call(strcspn, ptr1, ptr2)
}
}
func Test_memchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
clear(memory[:2*page])
ptr := (page - 8) + alignment
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
want := 0
if pos < length {
want = ptr + pos
}
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
want := len(memory) - 1
if length == 0 {
want = 0
}
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
func Test_strlen(t *testing.T) {
for length := range 64 {
for alignment := range 24 {
clear(memory[:2*page])
ptr := (page - 8) + alignment
clear(memory[:2*page])
fill(memory[ptr:ptr+length], 5)
got := call(strlen, uint64(ptr))
@@ -270,22 +207,71 @@ func Test_strlen(t *testing.T) {
}
}
func Test_strchr(t *testing.T) {
func Test_memchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
clear(memory[:2*page])
ptr := (page - 8) + alignment
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+length] = 0
want := 0
if pos < length {
want = ptr + pos
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
if pos >= 0 {
memory[ptr+pos+2] = 7
}
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
var want int
if length != 0 {
want = len(memory) - 1
got := call(memchr, uint64(ptr), 7, math.MaxUint32)
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint32(math.MaxUint32), uint32(got), uint32(want))
}
}
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
func Test_strchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := 0
if pos < length {
want = ptr + pos
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+pos+1] = 7
memory[ptr+length] = 0
got := call(strchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strchr(%d, %d) = %d, want %d",
@@ -312,21 +298,109 @@ func Test_strchr(t *testing.T) {
}
}
func Test_strrchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := 0
if pos < length {
want = ptr + pos
} else if length > 0 {
want = ptr
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr] = 7
memory[ptr+pos] = 7
memory[ptr+length] = 0
got := call(strrchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strrchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
ptr := len(memory) - length
want := len(memory) - 2
if length <= 1 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[ptr] = 7
memory[len(memory)-2] = 7
memory[len(memory)-1] = 0
got := call(strrchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strrchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
const (
compareTest1 = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\x14\xf4\x93\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x80\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x7f\xf3\x93\x01\x00\x01"
compareTest2 = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\xbc\x40\x96\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x7f\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x80\xf3\x93\x01\x00\x02"
)
func Test_memcmp(t *testing.T) {
const s1 = compareTest1
const s2 = compareTest2
ptr2 := len(memory) - len(s2)
clear(memory)
copy(memory[ptr1:], s1)
copy(memory[ptr2:], s2)
for i := range len(s1) + 1 {
for j := range len(s1) - i {
want := strings.Compare(s1[i:i+j], s2[i:i+j])
got := call(memcmp, uint64(ptr1+i), uint64(ptr2+i), uint64(j))
if sign(int32(got)) != want {
t.Errorf("strcmp(%d, %d, %d) = %d, want %d",
ptr1+i, ptr2+i, j, int32(got), want)
}
}
}
}
func Test_strspn(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
clear(memory[:2*page])
ptr := (page - 8) + alignment
want := min(pos, length)
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+length] = 0
memory[128] = 3
memory[128] = 7 | 128
memory[129] = 5
want := min(pos, length)
got := call(strspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
@@ -341,18 +415,18 @@ func Test_strspn(t *testing.T) {
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 3
memory[129] = 5
want := length - 1
if length == 0 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 7 | 128
memory[129] = 5
got := call(strspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
@@ -371,17 +445,16 @@ func Test_strcspn(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
clear(memory[:2*page])
ptr := (page - 8) + alignment
want := min(pos, length)
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+length] = 0
memory[128] = 3
memory[128] = 5 | 128
memory[129] = 7
want := min(pos, length)
got := call(strcspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
@@ -396,18 +469,18 @@ func Test_strcspn(t *testing.T) {
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 3
memory[129] = 7
want := length - 1
if length == 0 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 5 | 128
memory[129] = 7
got := call(strcspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
@@ -422,8 +495,338 @@ func Test_strcspn(t *testing.T) {
}
}
type searchTest struct {
haystk string
needle string
out int
}
var searchTests = []searchTest{
{"", "", 0},
{"", "a", -1},
{"", "foo", -1},
{"fo", "foo", -1},
{"foo", "baz", -1},
{"foo", "foo", 0},
{"oofofoofooo", "f", 2},
{"oofofoofooo", "foo", 4},
{"barfoobarfoo", "foo", 3},
{"foo", "", 0},
{"foo", "o", 1},
{"jrzm6jjhorimglljrea4w3rlgosts0w2gia17hno2td4qd1jz", "jz", 47},
{"ekkuk5oft4eq0ocpacknhwouic1uua46unx12l37nioq9wbpnocqks6", "ks6", 52},
{"999f2xmimunbuyew5vrkla9cpwhmxan8o98ec", "98ec", 33},
{"9lpt9r98i04k8bz6c6dsrthb96bhi", "96bhi", 24},
{"55u558eqfaod2r2gu42xxsu631xf0zobs5840vl", "5840vl", 33},
{"", "a", -1},
{"x", "a", -1},
{"x", "x", 0},
{"abc", "a", 0},
{"abc", "b", 1},
{"abc", "c", 2},
{"abc", "x", -1},
{"", "ab", -1},
{"bc", "ab", -1},
{"ab", "ab", 0},
{"xab", "ab", 1},
{"xab"[:2], "ab", -1},
{"", "abc", -1},
{"xbc", "abc", -1},
{"abc", "abc", 0},
{"xabc", "abc", 1},
{"xabc"[:3], "abc", -1},
{"xabxc", "abc", -1},
{"", "abcd", -1},
{"xbcd", "abcd", -1},
{"abcd", "abcd", 0},
{"xabcd", "abcd", 1},
{"xyabcd"[:5], "abcd", -1},
{"xbcqq", "abcqq", -1},
{"abcqq", "abcqq", 0},
{"xabcqq", "abcqq", 1},
{"xyabcqq"[:6], "abcqq", -1},
{"xabxcqq", "abcqq", -1},
{"xabcqxq", "abcqq", -1},
{"", "01234567", -1},
{"32145678", "01234567", -1},
{"01234567", "01234567", 0},
{"x01234567", "01234567", 1},
{"x0123456x01234567", "01234567", 9},
{"xx01234567"[:9], "01234567", -1},
{"", "0123456789", -1},
{"3214567844", "0123456789", -1},
{"0123456789", "0123456789", 0},
{"x0123456789", "0123456789", 1},
{"x012345678x0123456789", "0123456789", 11},
{"xyz0123456789"[:12], "0123456789", -1},
{"x01234567x89", "0123456789", -1},
{"", "0123456789012345", -1},
{"3214567889012345", "0123456789012345", -1},
{"0123456789012345", "0123456789012345", 0},
{"x0123456789012345", "0123456789012345", 1},
{"x012345678901234x0123456789012345", "0123456789012345", 17},
{"", "01234567890123456789", -1},
{"32145678890123456789", "01234567890123456789", -1},
{"01234567890123456789", "01234567890123456789", 0},
{"x01234567890123456789", "01234567890123456789", 1},
{"x0123456789012345678x01234567890123456789", "01234567890123456789", 21},
{"xyz01234567890123456789"[:22], "01234567890123456789", -1},
{"", "0123456789012345678901234567890", -1},
{"321456788901234567890123456789012345678911", "0123456789012345678901234567890", -1},
{"0123456789012345678901234567890", "0123456789012345678901234567890", 0},
{"x0123456789012345678901234567890", "0123456789012345678901234567890", 1},
{"x012345678901234567890123456789x0123456789012345678901234567890", "0123456789012345678901234567890", 32},
{"xyz0123456789012345678901234567890"[:33], "0123456789012345678901234567890", -1},
{"", "01234567890123456789012345678901", -1},
{"32145678890123456789012345678901234567890211", "01234567890123456789012345678901", -1},
{"01234567890123456789012345678901", "01234567890123456789012345678901", 0},
{"x01234567890123456789012345678901", "01234567890123456789012345678901", 1},
{"x0123456789012345678901234567890x01234567890123456789012345678901", "01234567890123456789012345678901", 33},
{"xyz01234567890123456789012345678901"[:34], "01234567890123456789012345678901", -1},
{"xxxxxx012345678901234567890123456789012345678901234567890123456789012", "012345678901234567890123456789012345678901234567890123456789012", 6},
{"", "0123456789012345678901234567890123456789", -1},
{"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456789", 2},
{"xx012345678901234567890123456789012345678901234567890123456789012"[:41], "0123456789012345678901234567890123456789", -1},
{"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456xxx", -1},
{"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx", "0123456789012345678901234567890123456xxx", 65},
{"barfoobarfooyyyzzzyyyzzzyyyzzzyyyxxxzzzyyy", "x", 33},
{"fofofofooofoboo", "oo", 7},
{"fofofofofofoboo", "ob", 11},
{"fofofofofofoboo", "boo", 12},
{"fofofofofofoboo", "oboo", 11},
{"fofofofofoooboo", "fooo", 8},
{"fofofofofofoboo", "foboo", 10},
{"fofofofofofoboo", "fofob", 8},
{"fofofofofofofoffofoobarfoo", "foffof", 12},
{"fofofofofoofofoffofoobarfoo", "foffof", 13},
{"fofofofofofofoffofoobarfoo", "foffofo", 12},
{"fofofofofoofofoffofoobarfoo", "foffofo", 13},
{"fofofofofoofofoffofoobarfoo", "foffofoo", 13},
{"fofofofofofofoffofoobarfoo", "foffofoo", 12},
{"fofofofofoofofoffofoobarfoo", "foffofoob", 13},
{"fofofofofofofoffofoobarfoo", "foffofoob", 12},
{"fofofofofoofofoffofoobarfoo", "foffofooba", 13},
{"fofofofofofofoffofoobarfoo", "foffofooba", 12},
{"fofofofofoofofoffofoobarfoo", "foffofoobar", 13},
{"fofofofofofofoffofoobarfoo", "foffofoobar", 12},
{"fofofofofoofofoffofoobarfoo", "foffofoobarf", 13},
{"fofofofofofofoffofoobarfoo", "foffofoobarf", 12},
{"fofofofofoofofoffofoobarfoo", "foffofoobarfo", 13},
{"fofofofofofofoffofoobarfoo", "foffofoobarfo", 12},
{"fofofofofoofofoffofoobarfoo", "foffofoobarfoo", 13},
{"fofofofofofofoffofoobarfoo", "foffofoobarfoo", 12},
{"fofofofofoofofoffofoobarfoo", "ofoffofoobarfoo", 12},
{"fofofofofofofoffofoobarfoo", "ofoffofoobarfoo", 11},
{"fofofofofoofofoffofoobarfoo", "fofoffofoobarfoo", 11},
{"fofofofofofofoffofoobarfoo", "fofoffofoobarfoo", 10},
{"fofofofofoofofoffofoobarfoo", "foobars", -1},
{"foofyfoobarfoobar", "y", 4},
{"oooooooooooooooooooooo", "r", -1},
{"oxoxoxoxoxoxoxoxoxoxoxoy", "oy", 22},
{"oxoxoxoxoxoxoxoxoxoxoxox", "oy", -1},
{"oxoxoxoxoxoxoxoxoxoxox☺", "☺", 22},
{"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx\xed\x9f\xc0", "\xed\x9f\xc0", 105},
{"000000000000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000001", 5},
}
func Fuzz_memchr(f *testing.F) {
f.Fuzz(func(t *testing.T, s string, c, i byte) {
if len(s) > 128 || int(i) > len(s) {
t.SkipNow()
}
copy(memory[ptr1:], s)
got := call(memchr, ptr1+uint64(i), uint64(c), uint64(len(s)-int(i)))
want := strings.IndexByte(s[i:], c)
if want >= 0 {
want = ptr1 + int(i) + want
} else {
want = 0
}
if uint32(got) != uint32(want) {
t.Errorf("memchr(%q, %q) = %d, want %d",
s[i:], c, uint32(got), uint32(want))
}
})
}
func Fuzz_strchr(f *testing.F) {
f.Fuzz(func(t *testing.T, s string, c, i byte) {
if len(s) > 128 || int(i) > len(s) {
t.SkipNow()
}
copy(memory[ptr1:], s)
memory[ptr1+len(s)] = 0
got := call(strchr, ptr1+uint64(i), uint64(c))
want := bytes.IndexByte(term1(memory[ptr1+uint64(i):]), c)
if want >= 0 {
want = ptr1 + int(i) + want
} else {
want = 0
}
if uint32(got) != uint32(want) {
t.Errorf("strchr(%q, %q) = %d, want %d",
s[i:], c, uint32(got), uint32(want))
}
})
}
func Fuzz_strrchr(f *testing.F) {
f.Fuzz(func(t *testing.T, s string, c, i byte) {
if len(s) > 128 || int(i) > len(s) {
t.SkipNow()
}
copy(memory[ptr1:], s)
memory[ptr1+len(s)] = 0
got := call(strrchr, ptr1+uint64(i), uint64(c))
want := bytes.LastIndexByte(term1(memory[ptr1+uint64(i):]), c)
if want >= 0 {
want = ptr1 + int(i) + want
} else {
want = 0
}
if uint32(got) != uint32(want) {
t.Errorf("strrchr(%q, %q) = %d, want %d",
s[i:], c, uint32(got), uint32(want))
}
})
}
func Fuzz_memcmp(f *testing.F) {
const s1 = compareTest1
const s2 = compareTest2
for i := range len(compareTest1) + 1 {
f.Add(s1[i:], s2[i:])
}
f.Fuzz(func(t *testing.T, s1, s2 string) {
if len(s1) > 128 || len(s1) != len(s2) {
t.SkipNow()
}
copy(memory[ptr1:], s1)
copy(memory[ptr2:], s2)
got := call(memcmp, uint64(ptr1), uint64(ptr2), uint64(len(s1)))
want := strings.Compare(s1, s2)
if sign(int32(got)) != want {
t.Errorf("memcmp(%q, %q) = %d, want %d",
s1, s2, uint32(got), uint32(want))
}
})
}
func Fuzz_strspn(f *testing.F) {
for _, t := range searchTests {
f.Add(t.haystk, t.needle)
}
f.Fuzz(func(t *testing.T, s, chars string) {
if len(s) > 128 || len(chars) > 128 {
t.SkipNow()
}
copy(memory[ptr1:], s)
copy(memory[ptr2:], chars)
memory[ptr1+len(s)] = 0
memory[ptr2+len(chars)] = 0
got := call(strspn, uint64(ptr1), uint64(ptr2))
s = term(s)
chars = term(chars)
want := indexNotByte(s, chars)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%v, %v) = %d, want %d",
[]byte(memory[ptr1:ptr1+len(s)]),
[]byte(memory[ptr2:ptr2+len(chars)]),
uint32(got), uint32(want))
}
})
}
func Fuzz_strcspn(f *testing.F) {
for _, t := range searchTests {
f.Add(t.haystk, t.needle)
}
f.Fuzz(func(t *testing.T, s, chars string) {
if len(s) > 128 || len(chars) > 128 {
t.SkipNow()
}
copy(memory[ptr1:], s)
copy(memory[ptr2:], chars)
memory[ptr1+len(s)] = 0
memory[ptr2+len(chars)] = 0
got := call(strcspn, uint64(ptr1), uint64(ptr2))
s = term(s)
chars = term(chars)
want := indexAnyByte(s, chars)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%q, %q) = %d, want %d",
s, chars, uint32(got), uint32(want))
}
})
}
func sign(x int32) int {
switch {
case x > 0:
return +1
case x < 0:
return -1
default:
return 0
}
}
func fill(s []byte, v byte) {
for i := range s {
s[i] = v
}
}
func term[T interface{ []byte | string }](s T) T {
for i, c := range []byte(s) {
if c == 0 {
return s[:i]
}
}
return s
}
func term1[T interface{ []byte | string }](s T) T {
for i, c := range []byte(s) {
if c == 0 {
return s[:i+1]
}
}
return s
}
func indexNotByte(s, chars string) int {
for i, c := range []byte(s) {
if strings.IndexByte(chars, c) < 0 {
return i
}
}
return len(s)
}
func indexAnyByte(s, chars string) int {
for i, c := range []byte(s) {
if strings.IndexByte(chars, c) >= 0 {
return i
}
}
return len(s)
}

36
sqlite3/libc/math.h Normal file
View File

@@ -0,0 +1,36 @@
#include_next <math.h> // the system math.h
#ifndef _WASM_SIMD128_MATH_H
#define _WASM_SIMD128_MATH_H
#include <wasm_simd128.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __wasm_relaxed_simd__
// This header assumes "relaxed fused multiply-add"
// is both faster and more precise.
#define FP_FAST_FMA 1
__attribute__((weak))
double fma(double x, double y, double z) {
// If we get a software implementation from the host,
// this is enough to short circuit it on the 2nd lane.
const v128_t wx = wasm_f64x2_replace_lane(b, 0, x);
const v128_t wy = wasm_f64x2_splat(y);
const v128_t wz = wasm_f64x2_splat(z);
const v128_t wr = wasm_f64x2_relaxed_madd(wx, wy, wz);
return wasm_f64x2_extract_lane(wr, 0);
}
#endif // __wasm_relaxed_simd__
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _WASM_SIMD128_MATH_H

View File

@@ -1,11 +1,8 @@
#include_next <stdlib.h> // the system stdlib.h
#ifndef _WASM_SIMD128_STDLIB_H
#define _WASM_SIMD128_STDLIB_H
#include <stddef.h>
#include <stdint.h>
#include_next <stdlib.h> // the system stdlib.h
#ifdef __cplusplus
extern "C" {
#endif
@@ -17,22 +14,31 @@ extern "C" {
__attribute__((weak))
void qsort(void *base, size_t nel, size_t width,
int (*comp)(const void *, const void *)) {
if (width == 0) return;
// If nel is zero, we're required to do nothing.
// If it's one, the array is already sorted.
size_t wnel = width * nel;
size_t gap = nel;
while (gap > 1) {
// Use 64-bit unsigned arithmetic to avoid intermediate overflow.
// Absent overflow, gap will be strictly less than its previous value.
// Once it is one or zero, set it to one: do a final pass, and stop.
gap = (5ull * gap - 1) / 11;
if (gap == 0) gap = 1;
// It'd be undefined behavior for wnel to overflow a size_t;
// or if width is zero: the base pointer would be invalid.
// Since gap is stricly less than nel, we can assume
// wgap is strictly less than wnel.
size_t wgap = width * gap;
__builtin_assume(wgap < wnel);
for (size_t i = wgap; i < wnel; i += width) {
// Even without overflow flags, the overflow builtin helps the compiler.
for (size_t j = i; !__builtin_sub_overflow(j, wgap, &j);) {
char *a = j + (char *)base;
char *b = a + wgap;
if (comp(a, b) <= 0) break;
// This well known loop is automatically vectorized.
size_t s = width;
do {
char tmp = *a;

View File

@@ -1,13 +1,10 @@
#include_next <string.h> // the system string.h
#ifndef _WASM_SIMD128_STRING_H
#define _WASM_SIMD128_STRING_H
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <wasm_simd128.h>
#include <__macro_PAGESIZE.h>
#include_next <string.h> // the system string.h
#ifdef __cplusplus
extern "C" {
@@ -17,19 +14,18 @@ extern "C" {
// Use the builtins if compiled with bulk memory operations.
// Clang will intrinsify using SIMD for small, constant N.
// For everything else, this helps inlining.
__attribute__((weak))
__attribute__((weak, always_inline))
void *memset(void *dest, int c, size_t n) {
return __builtin_memset(dest, c, n);
}
__attribute__((weak))
void *memcpy(void *restrict dest, const void *restrict src, size_t n) {
__attribute__((weak, always_inline))
void *memcpy(void *__restrict dest, const void *__restrict src, size_t n) {
return __builtin_memcpy(dest, src, n);
}
__attribute__((weak))
__attribute__((weak, always_inline))
void *memmove(void *dest, const void *src, size_t n) {
return __builtin_memmove(dest, src, n);
}
@@ -38,220 +34,177 @@ void *memmove(void *dest, const void *src, size_t n) {
#ifdef __wasm_simd128__
// SIMD versions of some string.h functions.
//
// These assume aligned v128_t loads can't fail,
// and so can't unaligned loads up to the last
// aligned address less than memory size.
//
// These also assume unaligned access is not painfully slow,
// but that bitmask extraction is really slow on AArch64.
__attribute__((weak))
int memcmp(const void *v1, const void *v2, size_t n) {
// memcmp can read up to n bytes from each object.
// Use unaligned loads to handle the case where
// the objects have mismatching alignments.
const v128_t *w1 = v1;
const v128_t *w2 = v2;
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
// Find any single bit difference.
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
break;
int memcmp(const void *vl, const void *vr, size_t n) {
// Scalar algorithm.
if (n < sizeof(v128_t)) {
const unsigned char *u1 = (unsigned char *)vl;
const unsigned char *u2 = (unsigned char *)vr;
while (n--) {
if (*u1 != *u2) return *u1 - *u2;
u1++;
u2++;
}
w1++;
w2++;
return 0;
}
// Continue byte-by-byte.
const unsigned char *u1 = (void *)w1;
const unsigned char *u2 = (void *)w2;
while (n--) {
if (*u1 != *u2) return *u1 - *u2;
u1++;
u2++;
// memcmp is allowed to read up to n bytes from each object.
// Find the first different character in the objects.
// Unaligned loads handle the case where the objects
// have mismatching alignments.
const v128_t *v1 = (v128_t *)vl;
const v128_t *v2 = (v128_t *)vr;
while (n) {
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(v1), wasm_v128_load(v2));
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(cmp)) {
// Find the offset of the first zero bit (little-endian).
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
const unsigned char *u1 = (unsigned char *)v1 + ctz;
const unsigned char *u2 = (unsigned char *)v2 + ctz;
// This may help the compiler if the function is inlined.
__builtin_assume(*u1 - *u2 != 0);
return *u1 - *u2;
}
// This makes n a multiple of sizeof(v128_t)
// for every iteration except the first.
size_t align = (n - 1) % sizeof(v128_t) + 1;
v1 = (v128_t *)((char *)v1 + align);
v2 = (v128_t *)((char *)v2 + align);
n -= align;
}
return 0;
}
__attribute__((weak))
void *memchr(const void *v, int c, size_t n) {
void *memchr(const void *s, int c, size_t n) {
// When n is zero, a function that locates a character finds no occurrence.
// Otherwise, decrement n to ensure __builtin_sub_overflow "overflows"
// Otherwise, decrement n to ensure sub_overflow overflows
// when n would go equal-to-or-below zero.
if (n-- == 0) {
if (!n--) {
return NULL;
}
// memchr must behave as if it reads characters sequentially
// and stops as soon as a match is found.
// Aligning ensures loads can't fail.
uintptr_t align = (uintptr_t)v % sizeof(v128_t);
const v128_t *w = (void *)(v - align);
const v128_t wc = wasm_i8x16_splat(c);
// Aligning ensures out of bounds loads are safe.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
uintptr_t addr = (uintptr_t)s - align;
v128_t vc = wasm_i8x16_splat(c);
while (true) {
const v128_t cmp = wasm_i8x16_eq(*w, wc);
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t cmp = wasm_i8x16_eq(v, vc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
// Clear the bits corresponding to alignment
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless we cleared them.
// Knowing this helps the compiler.
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask is zero because of alignment,
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
// We found a match, unless it is beyond the end of the object.
// Find the offset of the first one bit (little-endian).
// That's a match, unless it is beyond the end of the object.
// Recall that we decremented n, so less-than-or-equal-to is correct.
size_t ctz = __builtin_ctz(mask);
return ctz <= n + align ? (void *)w + ctz : NULL;
return ctz - align <= n ? (char *)s + (addr - (uintptr_t)s + ctz) : NULL;
}
}
// Decrement n; if it "overflows" we're done.
// Decrement n; if it overflows we're done.
if (__builtin_sub_overflow(n, sizeof(v128_t) - align, &n)) {
return NULL;
}
align = 0;
w++;
addr += sizeof(v128_t);
}
}
__attribute__((weak))
void *memrchr(const void *s, int c, size_t n) {
// memrchr is allowed to read up to n bytes from the object.
// Search backward for the last matching character.
const v128_t *v = (v128_t *)((char *)s + n);
const v128_t vc = wasm_i8x16_splat(c);
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(--v), vc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
// Find the offset of the last one bit (little-endian).
// The leading 16 bits of the bitmask are always zero,
// and to be ignored.
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 16;
return (char *)(v + 1) - (clz + 1);
}
}
// Scalar algorithm.
const char *a = (char *)v;
while (n--) {
if (*(--a) == (char)c) return (char *)a;
}
return NULL;
}
__attribute__((weak))
size_t strlen(const char *s) {
// strlen must stop as soon as it finds the terminator.
// Aligning ensures loads can't fail.
// Aligning ensures out of bounds loads are safe.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
const v128_t *w = (void *)(s - align);
uintptr_t addr = (uintptr_t)s - align;
while (true) {
for (;;) {
v128_t v = *(v128_t *)addr;
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(*w)) {
const v128_t cmp = wasm_i8x16_eq(*w, (v128_t){});
// Clear the bits corresponding to alignment
if (!wasm_i8x16_all_true(v)) {
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless we cleared them.
// Knowing this helps the compiler.
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
return (char *)w - s + __builtin_ctz(mask);
// Find the offset of the first one bit (little-endian).
return addr - (uintptr_t)s + __builtin_ctz(mask);
}
}
align = 0;
w++;
addr += sizeof(v128_t);
}
}
static int __strcmp(const char *s1, const char *s2) {
// Set limit to the largest possible valid v128_t pointer.
// Unsigned modular arithmetic gives the correct result
// unless memory size is zero, in which case all pointers are invalid.
const v128_t *const limit =
(v128_t *)(__builtin_wasm_memory_size(0) * PAGESIZE) - 1;
// Use unaligned loads to handle the case where
// the strings have mismatching alignments.
const v128_t *w1 = (void *)s1;
const v128_t *w2 = (void *)s2;
while (w1 <= limit && w2 <= limit) {
// Find any single bit difference.
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
break;
}
// All bytes are equal.
// If any byte is zero (on both strings) the strings are equal.
if (!wasm_i8x16_all_true(wasm_v128_load(w1))) {
return 0;
}
w1++;
w2++;
}
// Continue byte-by-byte.
const unsigned char *u1 = (void *)w1;
const unsigned char *u2 = (void *)w2;
while (true) {
if (*u1 != *u2) return *u1 - *u2;
if (*u1 == 0) break;
u1++;
u2++;
}
return 0;
}
__attribute__((weak, always_inline))
int strcmp(const char *s1, const char *s2) {
// Use strncmp when comparing against literal strings.
// If the literal is small, the vector search will be skipped.
if (__builtin_constant_p(strlen(s2))) {
return strncmp(s1, s2, strlen(s2));
}
return __strcmp(s1, s2);
}
__attribute__((weak))
int strncmp(const char *s1, const char *s2, size_t n) {
// Set limit to the largest possible valid v128_t pointer.
// Unsigned modular arithmetic gives the correct result
// unless memory size is zero, in which case all pointers are invalid.
const v128_t *const limit =
(v128_t *)(__builtin_wasm_memory_size(0) * PAGESIZE) - 1;
// Use unaligned loads to handle the case where
// the strings have mismatching alignments.
const v128_t *w1 = (void *)s1;
const v128_t *w2 = (void *)s2;
for (; w1 <= limit && w2 <= limit && n >= sizeof(v128_t); n -= sizeof(v128_t)) {
// Find any single bit difference.
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
break;
}
// All bytes are equal.
// If any byte is zero (on both strings) the strings are equal.
if (!wasm_i8x16_all_true(wasm_v128_load(w1))) {
return 0;
}
w1++;
w2++;
}
// Continue byte-by-byte.
const unsigned char *u1 = (void *)w1;
const unsigned char *u2 = (void *)w2;
while (n--) {
if (*u1 != *u2) return *u1 - *u2;
if (*u1 == 0) break;
u1++;
u2++;
}
return 0;
}
static char *__strchrnul(const char *s, int c) {
// strchrnul must stop as soon as a match is found.
// Aligning ensures loads can't fail.
// Aligning ensures out of bounds loads are safe.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
const v128_t *w = (void *)(s - align);
const v128_t wc = wasm_i8x16_splat(c);
uintptr_t addr = (uintptr_t)s - align;
v128_t vc = wasm_i8x16_splat(c);
while (true) {
const v128_t cmp = wasm_i8x16_eq(*w, (v128_t){}) | wasm_i8x16_eq(*w, wc);
for (;;) {
v128_t v = *(v128_t *)addr;
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){}) | wasm_i8x16_eq(v, vc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
// Clear the bits corresponding to alignment
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless we cleared them.
// Knowing this helps the compiler.
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
return (char *)w + __builtin_ctz(mask);
// Find the offset of the first one bit (little-endian).
return (char *)s + (addr - (uintptr_t)s + __builtin_ctz(mask));
}
}
align = 0;
w++;
addr += sizeof(v128_t);
}
}
@@ -271,97 +224,252 @@ char *strchr(const char *s, int c) {
return (char *)s + strlen(s);
}
char *r = __strchrnul(s, c);
return *(char *)r == (char)c ? r : NULL;
return *r == (char)c ? r : NULL;
}
__attribute__((weak, always_inline))
char *strrchr(const char *s, int c) {
// For finding the terminator, strlen is faster.
if (__builtin_constant_p(c) && (char)c == 0) {
return (char *)s + strlen(s);
}
// This could also be implemented in a single pass using strchr,
// advancing to the next match until no more matches are found.
// That would be suboptimal with lots of consecutive matches.
return (char *)memrchr(s, c, strlen(s) + 1);
}
// SIMDized check which bytes are in a set (Geoff Langdale)
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
// This is the same algorithm as truffle from Hyperscan:
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/truffle.c#L64-L81
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/trufflecompile.cpp
typedef struct {
__u8x16 lo;
__u8x16 hi;
} __wasm_v128_bitmap256_t;
__attribute__((always_inline))
static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, uint8_t i) {
uint8_t hi_nibble = i >> 4;
uint8_t lo_nibble = i & 0xf;
bitmap->lo[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 0));
bitmap->hi[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 8));
}
#ifndef __wasm_relaxed_simd__
#define wasm_i8x16_relaxed_swizzle wasm_i8x16_swizzle
#endif // __wasm_relaxed_simd__
__attribute__((always_inline))
static v128_t __wasm_v128_chkbits(__wasm_v128_bitmap256_t bitmap, v128_t v) {
v128_t hi_nibbles = wasm_u8x16_shr(v, 4);
v128_t bitmask_lookup = wasm_u64x2_const_splat(0x8040201008040201);
v128_t bitmask = wasm_i8x16_relaxed_swizzle(bitmask_lookup, hi_nibbles);
v128_t indices_0_7 = v & wasm_u8x16_const_splat(0x8f);
v128_t indices_8_15 = indices_0_7 ^ wasm_u8x16_const_splat(0x80);
v128_t row_0_7 = wasm_i8x16_swizzle((v128_t)bitmap.lo, indices_0_7);
v128_t row_8_15 = wasm_i8x16_swizzle((v128_t)bitmap.hi, indices_8_15);
v128_t bitsets = row_0_7 | row_8_15;
return bitsets & bitmask;
}
#undef wasm_i8x16_relaxed_swizzle
__attribute__((weak))
size_t strspn(const char *s, const char *c) {
#ifndef _REENTRANT
static // Avoid the stack for builds without threads.
#endif
char byteset[UCHAR_MAX + 1];
const char *const a = s;
// strspn must stop as soon as it finds the terminator.
// Aligning ensures out of bounds loads are safe.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
uintptr_t addr = (uintptr_t)s - align;
if (!c[0]) return 0;
if (!c[1]) {
// Set limit to the largest possible valid v128_t pointer.
// Unsigned modular arithmetic gives the correct result
// unless memory size is zero, in which case all pointers are invalid.
const v128_t *const limit =
(v128_t *)(__builtin_wasm_memory_size(0) * PAGESIZE) - 1;
const v128_t *w = (void *)s;
const v128_t wc = wasm_i8x16_splat(*c);
while (w <= limit) {
if (!wasm_i8x16_all_true(wasm_i8x16_eq(wasm_v128_load(w), wc))) {
break;
v128_t vc = wasm_i8x16_splat(*c);
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t cmp = wasm_i8x16_eq(v, vc);
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(cmp)) {
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
// Find the offset of the first one bit (little-endian).
return addr - (uintptr_t)s + __builtin_ctz(mask);
}
}
w++;
align = 0;
addr += sizeof(v128_t);
}
s = (void *)w;
while (*s == *c) s++;
return s - a;
}
#if !__OPTIMIZE__ || __OPTIMIZE_SIZE__
__wasm_v128_bitmap256_t bitmap = {};
// Unoptimized version.
memset(byteset, 0, sizeof(byteset));
while (*c && (byteset[*(unsigned char *)c] = 1)) c++;
while (byteset[*(unsigned char *)s]) s++;
for (; *c; c++) {
// Terminator IS NOT on the bitmap.
__wasm_v128_setbit(&bitmap, (uint8_t)*c);
}
#else
// This is faster than memset.
volatile v128_t *w = (void *)byteset;
#pragma unroll
for (size_t i = sizeof(byteset) / sizeof(v128_t); i--;) w[i] = (v128_t){};
static_assert(sizeof(byteset) % sizeof(v128_t) == 0);
// Keeping byteset[0] = 0 avoids the other loop having to test for it.
while (*c && (byteset[*(unsigned char *)c] = 1)) c++;
#pragma unroll 4
while (byteset[*(unsigned char *)s]) s++;
#endif
return s - a;
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
// Find the offset of the first one bit (little-endian).
return addr - (uintptr_t)s + __builtin_ctz(mask);
}
}
align = 0;
addr += sizeof(v128_t);
}
}
__attribute__((weak))
size_t strcspn(const char *s, const char *c) {
#ifndef _REENTRANT
static // Avoid the stack for builds without threads.
#endif
char byteset[UCHAR_MAX + 1];
const char *const a = s;
if (!c[0] || !c[1]) return __strchrnul(s, *c) - s;
#if !__OPTIMIZE__ || __OPTIMIZE_SIZE__
// strcspn must stop as soon as it finds the terminator.
// Aligning ensures out of bounds loads are safe.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
uintptr_t addr = (uintptr_t)s - align;
// Unoptimized version.
memset(byteset, 0, sizeof(byteset));
while ((byteset[*(unsigned char *)c] = 1) && *c) c++;
while (!byteset[*(unsigned char *)s]) s++;
__wasm_v128_bitmap256_t bitmap = {};
#else
do {
// Terminator IS on the bitmap.
__wasm_v128_setbit(&bitmap, (uint8_t)*c);
} while (*c++);
// This is faster than memset.
volatile v128_t *w = (void *)byteset;
#pragma unroll
for (size_t i = sizeof(byteset) / sizeof(v128_t); i--;) w[i] = (v128_t){};
static_assert(sizeof(byteset) % sizeof(v128_t) == 0);
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless align cleared them.
// Knowing this helps the compiler if it unrolls the loop.
__builtin_assume(mask || align);
// If the mask became zero because of align,
// it's as if we didn't find anything.
if (mask) {
// Find the offset of the first one bit (little-endian).
return addr - (uintptr_t)s + __builtin_ctz(mask);
}
}
align = 0;
addr += sizeof(v128_t);
}
}
// Setting byteset[0] = 1 avoids the other loop having to test for it.
while ((byteset[*(unsigned char *)c] = 1) && *c) c++;
#pragma unroll 4
while (!byteset[*(unsigned char *)s]) s++;
// Given the above SIMD implementations,
// these are best implemented as
// small wrappers over those functions.
#endif
// Simple wrappers already in musl:
// - mempcpy
// - strcat
// - strlcat
// - strdup
// - strndup
// - strnlen
// - strpbrk
// - strsep
// - strtok
return s - a;
__attribute__((weak))
void *memccpy(void *__restrict dest, const void *__restrict src, int c,
size_t n) {
const void *m = memchr(src, c, n);
if (m != NULL) {
n = (char *)m - (char *)src + 1;
m = (char *)dest + n;
}
memcpy(dest, src, n);
return (void *)m;
}
__attribute__((weak))
size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n) {
size_t slen = strlen(src);
if (n--) {
if (n > slen) n = slen;
memcpy(dest, src, n);
dest[n] = 0;
}
return slen;
}
__attribute__((weak))
char *strncat(char *__restrict dest, const char *__restrict src, size_t n) {
size_t strnlen(const char *s, size_t n);
size_t dlen = strlen(dest);
size_t slen = strnlen(src, n);
memcpy(dest + dlen, src, slen);
dest[dlen + slen] = 0;
return dest;
}
static char *__stpcpy(char *__restrict dest, const char *__restrict src) {
size_t slen = strlen(src);
memcpy(dest, src, slen + 1);
return dest + slen;
}
static char *__stpncpy(char *__restrict dest, const char *__restrict src,
size_t n) {
size_t strnlen(const char *s, size_t n);
size_t slen = strnlen(src, n);
memcpy(dest, src, slen);
memset(dest + slen, 0, n - slen);
return dest + slen;
}
__attribute__((weak, always_inline))
char *stpcpy(char *__restrict dest, const char *__restrict src) {
return __stpcpy(dest, src);
}
__attribute__((weak, always_inline))
char *strcpy(char *__restrict dest, const char *__restrict src) {
__stpcpy(dest, src);
return dest;
}
__attribute__((weak, always_inline))
char *stpncpy(char *__restrict dest, const char *__restrict src, size_t n) {
return __stpncpy(dest, src, n);
}
__attribute__((weak, always_inline))
char *strncpy(char *__restrict dest, const char *__restrict src, size_t n) {
__stpncpy(dest, src, n);
return dest;
}
#endif // __wasm_simd128__

View File

@@ -26,8 +26,8 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then
fi
fi
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-$WASI_SDK.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-$BINARYEN.tar.gz"
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-28/wasi-sdk-28.0-$WASI_SDK.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-$BINARYEN.tar.gz"
# Download tools
mkdir -p "$ROOT/tools"

View File

@@ -1,7 +1,7 @@
# Remove VFS registration. Go handles it.
--- sqlite3.c.orig
+++ sqlite3.c
@@ -26725,7 +26725,7 @@
@@ -27176,7 +27176,7 @@
sqlite3_free(p);
return sqlite3_os_init();
}
@@ -10,7 +10,7 @@
/*
** The list of all registered VFS implementations.
*/
@@ -26822,7 +26822,7 @@
@@ -27273,7 +27273,7 @@
sqlite3_mutex_leave(mutex);
return SQLITE_OK;
}

39
stmt.go
View File

@@ -1,9 +1,7 @@
package sqlite3
import (
"encoding/json"
"math"
"strconv"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
@@ -362,18 +360,6 @@ func (s *Stmt) BindPointer(param int, ptr any) error {
return s.c.error(rc)
}
// BindJSON binds the JSON encoding of value to the prepared statement.
// The leftmost SQL parameter has an index of 1.
//
// https://sqlite.org/c3ref/bind_blob.html
func (s *Stmt) BindJSON(param int, value any) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return s.BindRawText(param, data)
}
// BindValue binds a copy of value to the prepared statement.
// The leftmost SQL parameter has an index of 1.
//
@@ -600,30 +586,6 @@ func (s *Stmt) columnRawBytes(col int, ptr ptr_t, nul int32) []byte {
return util.View(s.c.mod, ptr, int64(n+nul))[:n]
}
// ColumnJSON parses the JSON-encoded value of the result column
// and stores it in the value pointed to by ptr.
// The leftmost column of the result set has the index 0.
//
// https://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ColumnJSON(col int, ptr any) error {
var data []byte
switch s.ColumnType(col) {
case NULL:
data = []byte("null")
case TEXT:
data = s.ColumnRawText(col)
case BLOB:
data = s.ColumnRawBlob(col)
case INTEGER:
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
case FLOAT:
data = util.AppendNumber(nil, s.ColumnFloat(col))
default:
panic(util.AssertErr())
}
return json.Unmarshal(data, ptr)
}
// ColumnValue returns the unprotected value of the result column.
// The leftmost column of the result set has the index 0.
//
@@ -633,7 +595,6 @@ func (s *Stmt) ColumnValue(col int) Value {
stk_t(s.handle), stk_t(col)))
return Value{
c: s.c,
unprot: true,
handle: ptr,
}
}

View File

@@ -45,10 +45,7 @@ func TestBlob(t *testing.T) {
}
var data [1280]byte
_, err = rand.Read(data[:])
if err != nil {
t.Fatal(err)
}
rand.Read(data[:])
_, err = blob.Write(data[:size/2])
if err != nil {

Some files were not shown because too many files have changed in this diff Show More