diff --git a/clib/main.go b/clib/main.go index 155e64f..1de845e 100644 --- a/clib/main.go +++ b/clib/main.go @@ -5,7 +5,6 @@ package main import "C" import ( - "fmt" "strings" "unsafe" @@ -30,7 +29,6 @@ func InitSQLite(params *C.char) { func CreateRowIterator(data *C.char, size C.int) C.int { var it = &facade.RowsIter{} input := C.GoBytes(unsafe.Pointer(data), size) - fmt.Println("input", input) it.Exec(input) ptr := C.int(len(iterators)) iterators[len(iterators)] = it diff --git a/client/Builder.ts b/client/Builder.ts index 2cdcff5..31b86ec 100644 --- a/client/Builder.ts +++ b/client/Builder.ts @@ -97,11 +97,11 @@ export default class Builder any> { this.methodCalls.set("Find", [filter]); return this; } - Select(fields: string[]): Builder { - this.methodCalls.set("Select", fields); + Select(fields: Record): Builder { + this.methodCalls.set("Select", [fields]); return this; } - Fields(fields: string[]): Builder { + Fields(fields: Record): Builder { this.Select(fields); return this; } @@ -207,6 +207,6 @@ export default class Builder any> { throw new Error(await response.text()); } const buf = await response.arrayBuffer(); - return decodeResponse(new Uint8Array(buf)); + return decodeResponse(new Uint8Array(buf))!; } } diff --git a/client/Bunding.ts b/client/Bunding.ts index cff2cfa..e38e88a 100644 --- a/client/Bunding.ts +++ b/client/Bunding.ts @@ -5,7 +5,7 @@ import { dlopen, FFIType, suffix, ptr, toBuffer } from "bun:ffi"; import { join } from "path"; const libname = `clib.${suffix}`; -const libpath = join("clib", libname); +const libpath = join(import.meta.dir, "..", "clib", libname); const { symbols: { InitSQLite, CreateRowIterator, NextRow, GetLen, Free, Cleanup }, diff --git a/client/Protocol.ts b/client/Protocol.ts index da65ecb..9d1cbab 100644 --- a/client/Protocol.ts +++ b/client/Protocol.ts @@ -31,47 +31,55 @@ export function encodeRequest(request: Request): Uint8Array { return encode(request); } -export function decodeResponse(input: Uint8Array): ExecResult { - const res = decode(input) as { - i: number; - ra: number; - li: number; - m?: string; - }; - return { - Id: res.i, - RowsAffected: res.ra, - LastInsertId: res.li, - Msg: res.m, - }; +export function decodeResponse(input: Uint8Array): ExecResult | null { + try { + const res = decode(input) as { + i: number; + ra: number; + li: number; + m?: string; + }; + return { + Id: res.i, + RowsAffected: res.ra, + LastInsertId: res.li, + Msg: res.m, + }; + } catch (e) { + return null; + } } const ROW_TAG = [0x81, 0xa1, 0x72]; -export function decodeRows(input: Uint8Array): Row[] { - const rows = []; - let count = 0; - let buf = []; - while (count < input.length) { - if (input.at(count) != 0x81) { - buf.push(input.at(count)); - count++; - continue; - } - const [a, b, c] = ROW_TAG; - const [aa, bb, cc] = input.slice(count, count + 4); - if (aa == a && bb == b && cc == c) { - rows.push([...ROW_TAG, ...buf]); - buf = []; - count += 3; - } else { - buf.push(input.at(count)); - count++; +export function decodeRows(input: Uint8Array): Row[] | null { + try { + const rows = []; + let count = 0; + let buf = []; + while (count < input.length) { + if (input.at(count) != 0x81) { + buf.push(input.at(count)); + count++; + continue; + } + const [a, b, c] = ROW_TAG; + const [aa, bb, cc] = input.slice(count, count + 4); + if (aa == a && bb == b && cc == c) { + rows.push([...ROW_TAG, ...buf]); + buf = []; + count += 3; + } else { + buf.push(input.at(count)); + count++; + } } + rows.push([...ROW_TAG, ...buf]); + rows.shift(); + return rows.map((row) => decode(new Uint8Array(row as number[]))) as Row[]; + } catch (e) { + return null; } - rows.push([...ROW_TAG, ...buf]); - rows.shift(); - return rows.map((row) => decode(new Uint8Array(row as number[]))) as Row[]; } export async function* decodeRowsIterator( @@ -84,7 +92,7 @@ export async function* decodeRowsIterator( break; } const rows = decodeRows(value); - for (const row of rows) { + for (const row of rows!) { yield row; } } diff --git a/client/libdal.ts b/client/libdal.ts index 9ad51d7..66d88f6 100644 --- a/client/libdal.ts +++ b/client/libdal.ts @@ -1,8 +1,13 @@ import Builder from "./Builder"; -import Binding from "./Library"; +import Bunding from "./Library"; import { encodeRequest, decodeRows, decodeResponse } from "./Protocol"; import type { ExecResult } from "./Protocol"; +//@ts-ignore +const Binding = Bunding.default ?? Bunding; + +Binding.initSQLite(Buffer.from(" ")); + type Options = { database: string; }; @@ -24,11 +29,15 @@ export default class CBuilder< const req = Buffer.from(encodeRequest(this.request)); const iter = Binding.rowIterator(req); for (;;) { - const response = iter.next(); + const response = iter.next() as Buffer; + const error = decodeResponse(response); + if (error?.Msg) { + throw new Error(error.Msg); + } const rows = decodeRows(response); - if (rows.length === 0) { + if (!rows || rows.length === 0) { iter.cleanup(); - break; + return; } for (const row of rows) { if (this.headerRow === null) { @@ -52,6 +61,6 @@ export default class CBuilder< const req = Buffer.from(encodeRequest(this.request)); const iter = Binding.rowIterator(req); const response = iter.next(); - return decodeResponse(response); + return decodeResponse(response)!; } } diff --git a/examples/bun/README.md b/examples/bun/README.md index 5ac5285..5c2bdec 100644 --- a/examples/bun/README.md +++ b/examples/bun/README.md @@ -1,4 +1,4 @@ -# bun-dal-exmaple +# bun-dal-example To install dependencies: diff --git a/examples/bun/find.test.ts b/examples/bun/find.test.ts new file mode 100644 index 0000000..f831320 --- /dev/null +++ b/examples/bun/find.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test"; +import path from "path"; +import DAL from "@nesterow/dal/client/libdal"; + +// in this case we need to use absolute path +const DATABASE_PATH = path.join(import.meta.dir, "..", "data", "chinook.db"); + +const db = new DAL({ + database: DATABASE_PATH, +}); + +describe("Query Interface", () => { + test(".Find", async () => { + const items = db + .In("artists") + .Find({ + name: { $glob: "A*" }, + }) + .Limit(10) + .Rows(); + + for await (const item of items) { + console.log(item); + } + + expect(true).toBe(true); + }); + + test(".Find.As", async () => { + class Artist { + ArtistId = 0; + Name = ""; + } + + const items = db + .In("artists") + .Find({ + name: { $glob: "B*" }, + }) + .As(Artist) + .Limit(1) + .Rows(); + + for await (const item of items) { + console.log(item); + } + console.log("done"); + expect(true).toBe(true); + }); +}); diff --git a/examples/bun/index.ts b/examples/bun/index.ts deleted file mode 100644 index f67b2c6..0000000 --- a/examples/bun/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello via Bun!"); \ No newline at end of file diff --git a/examples/bun/join.test.ts b/examples/bun/join.test.ts new file mode 100644 index 0000000..fdc9246 --- /dev/null +++ b/examples/bun/join.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from "bun:test"; +import path from "path"; +import DAL from "@nesterow/dal/client/libdal"; + +// in this case we need to use absolute path +const DATABASE_PATH = path.join(import.meta.dir, "..", "data", "chinook.db"); + +const db = new DAL({ + database: DATABASE_PATH, +}); + +describe("Query Interface", () => { + test(".Join [album < artist < tracks (find tracks for all artists whose names start with 'A')]", async () => { + class Album { + TrackId = 0; + TrackName = ""; + ArtistName = ""; + AlbumTitle = ""; + } + const items = db + .In("albums al") + .Join( + { + $for: "artists ar", + $do: { + "al.ArtistId": "ar.ArtistId", + }, + }, + { + $for: "tracks tr", + $do: { + "al.AlbumId": "tr.AlbumId", + }, + }, + ) + .Find({ + "ar.Name": { $glob: "A*" }, + }) + .Fields({ + "tr.TrackId" : "TrackId", + "tr.Name" : "TrackName", + "ar.Name" : "ArtistName", + "al.Title" : "AlbumTitle", + }) + .Limit(10) + .As(Album) + .Rows(); + + for await (const item of items) { + console.log(item); + } + + expect(true).toBe(true); + }); +}); diff --git a/examples/bun/package.json b/examples/bun/package.json index 5f89ea8..2e87236 100644 --- a/examples/bun/package.json +++ b/examples/bun/package.json @@ -11,4 +11,4 @@ "dependencies": { "@nesterow/dal": "../.." } -} \ No newline at end of file +} diff --git a/examples/data/.gitignore b/examples/data/.gitignore new file mode 100644 index 0000000..01cbcf3 --- /dev/null +++ b/examples/data/.gitignore @@ -0,0 +1,5 @@ +chinook.db-shm +chinook.db-wal +inserts.db +inserts.db-shm +inserts.db-wal \ No newline at end of file diff --git a/examples/data/chinook.db b/examples/data/chinook.db index 38a98b3..b17b2f3 100644 Binary files a/examples/data/chinook.db and b/examples/data/chinook.db differ diff --git a/examples/data/readme.md b/examples/data/readme.md index 95155d6..ee60533 100644 --- a/examples/data/readme.md +++ b/examples/data/readme.md @@ -1,3 +1,3 @@ # Chinook -A sample database taken from [SQLite Tutorial](https://www.sqlitetutorial.net/) \ No newline at end of file +A sample database taken from [SQLite Tutorial](https://www.sqlitetutorial.net/) diff --git a/pkg/adapter/DBAdapter.go b/pkg/adapter/DBAdapter.go index 2d84552..b3e2f05 100644 --- a/pkg/adapter/DBAdapter.go +++ b/pkg/adapter/DBAdapter.go @@ -114,6 +114,7 @@ func (a *DBAdapter) CleanUp() { } func (a *DBAdapter) Query(req Query) (*sql.Rows, error) { + fmt.Println(req) db, err := a.Open(req.Db) if err != nil { return nil, err @@ -132,6 +133,7 @@ func (a *DBAdapter) Query(req Query) (*sql.Rows, error) { if err != nil { return nil, err } + return sfmt.Query(req.Data...) } diff --git a/pkg/builder/Builder.go b/pkg/builder/Builder.go index 1cffef4..4aa4907 100644 --- a/pkg/builder/Builder.go +++ b/pkg/builder/Builder.go @@ -116,12 +116,12 @@ func (b *Builder) Sort(sort Map) *Builder { return b } -func (b *Builder) Limit(limit int) *Builder { +func (b *Builder) Limit(limit int64) *Builder { b.Parts.LimitExp = fmt.Sprintf("LIMIT %d", limit) return b } -func (b *Builder) Offset(offset int) *Builder { +func (b *Builder) Offset(offset int64) *Builder { b.Parts.OffsetExp = fmt.Sprintf("OFFSET %d", offset) return b }