[feat] add query example

This commit is contained in:
Anton Nesterov 2024-09-02 12:37:34 +02:00
parent ae625a848d
commit 059fe4ca76
No known key found for this signature in database
GPG key ID: 59121E8AE2851FB5
15 changed files with 180 additions and 54 deletions

View file

@ -5,7 +5,6 @@ package main
import "C" import "C"
import ( import (
"fmt"
"strings" "strings"
"unsafe" "unsafe"
@ -30,7 +29,6 @@ func InitSQLite(params *C.char) {
func CreateRowIterator(data *C.char, size C.int) C.int { func CreateRowIterator(data *C.char, size C.int) C.int {
var it = &facade.RowsIter{} var it = &facade.RowsIter{}
input := C.GoBytes(unsafe.Pointer(data), size) input := C.GoBytes(unsafe.Pointer(data), size)
fmt.Println("input", input)
it.Exec(input) it.Exec(input)
ptr := C.int(len(iterators)) ptr := C.int(len(iterators))
iterators[len(iterators)] = it iterators[len(iterators)] = it

View file

@ -97,11 +97,11 @@ export default class Builder<I extends abstract new (...args: any) => any> {
this.methodCalls.set("Find", [filter]); this.methodCalls.set("Find", [filter]);
return this; return this;
} }
Select(fields: string[]): Builder<I> { Select(fields: Record<string, any>): Builder<I> {
this.methodCalls.set("Select", fields); this.methodCalls.set("Select", [fields]);
return this; return this;
} }
Fields(fields: string[]): Builder<I> { Fields(fields: Record<string, any>): Builder<I> {
this.Select(fields); this.Select(fields);
return this; return this;
} }
@ -207,6 +207,6 @@ export default class Builder<I extends abstract new (...args: any) => any> {
throw new Error(await response.text()); throw new Error(await response.text());
} }
const buf = await response.arrayBuffer(); const buf = await response.arrayBuffer();
return decodeResponse(new Uint8Array(buf)); return decodeResponse(new Uint8Array(buf))!;
} }
} }

View file

@ -5,7 +5,7 @@ import { dlopen, FFIType, suffix, ptr, toBuffer } from "bun:ffi";
import { join } from "path"; import { join } from "path";
const libname = `clib.${suffix}`; const libname = `clib.${suffix}`;
const libpath = join("clib", libname); const libpath = join(import.meta.dir, "..", "clib", libname);
const { const {
symbols: { InitSQLite, CreateRowIterator, NextRow, GetLen, Free, Cleanup }, symbols: { InitSQLite, CreateRowIterator, NextRow, GetLen, Free, Cleanup },

View file

@ -31,47 +31,55 @@ export function encodeRequest(request: Request): Uint8Array {
return encode(request); return encode(request);
} }
export function decodeResponse(input: Uint8Array): ExecResult { export function decodeResponse(input: Uint8Array): ExecResult | null {
const res = decode(input) as { try {
i: number; const res = decode(input) as {
ra: number; i: number;
li: number; ra: number;
m?: string; li: number;
}; m?: string;
return { };
Id: res.i, return {
RowsAffected: res.ra, Id: res.i,
LastInsertId: res.li, RowsAffected: res.ra,
Msg: res.m, LastInsertId: res.li,
}; Msg: res.m,
};
} catch (e) {
return null;
}
} }
const ROW_TAG = [0x81, 0xa1, 0x72]; const ROW_TAG = [0x81, 0xa1, 0x72];
export function decodeRows(input: Uint8Array): Row[] { export function decodeRows(input: Uint8Array): Row[] | null {
const rows = []; try {
let count = 0; const rows = [];
let buf = []; let count = 0;
while (count < input.length) { let buf = [];
if (input.at(count) != 0x81) { while (count < input.length) {
buf.push(input.at(count)); if (input.at(count) != 0x81) {
count++; buf.push(input.at(count));
continue; count++;
} continue;
const [a, b, c] = ROW_TAG; }
const [aa, bb, cc] = input.slice(count, count + 4); const [a, b, c] = ROW_TAG;
if (aa == a && bb == b && cc == c) { const [aa, bb, cc] = input.slice(count, count + 4);
rows.push([...ROW_TAG, ...buf]); if (aa == a && bb == b && cc == c) {
buf = []; rows.push([...ROW_TAG, ...buf]);
count += 3; buf = [];
} else { count += 3;
buf.push(input.at(count)); } else {
count++; 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( export async function* decodeRowsIterator(
@ -84,7 +92,7 @@ export async function* decodeRowsIterator(
break; break;
} }
const rows = decodeRows(value); const rows = decodeRows(value);
for (const row of rows) { for (const row of rows!) {
yield row; yield row;
} }
} }

View file

@ -1,8 +1,13 @@
import Builder from "./Builder"; import Builder from "./Builder";
import Binding from "./Library"; import Bunding from "./Library";
import { encodeRequest, decodeRows, decodeResponse } from "./Protocol"; import { encodeRequest, decodeRows, decodeResponse } from "./Protocol";
import type { ExecResult } from "./Protocol"; import type { ExecResult } from "./Protocol";
//@ts-ignore
const Binding = Bunding.default ?? Bunding;
Binding.initSQLite(Buffer.from(" "));
type Options = { type Options = {
database: string; database: string;
}; };
@ -24,11 +29,15 @@ export default class CBuilder<
const req = Buffer.from(encodeRequest(this.request)); const req = Buffer.from(encodeRequest(this.request));
const iter = Binding.rowIterator(req); const iter = Binding.rowIterator(req);
for (;;) { 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); const rows = decodeRows(response);
if (rows.length === 0) { if (!rows || rows.length === 0) {
iter.cleanup(); iter.cleanup();
break; return;
} }
for (const row of rows) { for (const row of rows) {
if (this.headerRow === null) { if (this.headerRow === null) {
@ -52,6 +61,6 @@ export default class CBuilder<
const req = Buffer.from(encodeRequest(this.request)); const req = Buffer.from(encodeRequest(this.request));
const iter = Binding.rowIterator(req); const iter = Binding.rowIterator(req);
const response = iter.next(); const response = iter.next();
return decodeResponse(response); return decodeResponse(response)!;
} }
} }

View file

@ -1,4 +1,4 @@
# bun-dal-exmaple # bun-dal-example
To install dependencies: To install dependencies:

50
examples/bun/find.test.ts Normal file
View file

@ -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);
});
});

View file

@ -1 +0,0 @@
console.log("Hello via Bun!");

55
examples/bun/join.test.ts Normal file
View file

@ -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);
});
});

View file

@ -11,4 +11,4 @@
"dependencies": { "dependencies": {
"@nesterow/dal": "../.." "@nesterow/dal": "../.."
} }
} }

5
examples/data/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
chinook.db-shm
chinook.db-wal
inserts.db
inserts.db-shm
inserts.db-wal

Binary file not shown.

View file

@ -1,3 +1,3 @@
# Chinook # Chinook
A sample database taken from [SQLite Tutorial](https://www.sqlitetutorial.net/) A sample database taken from [SQLite Tutorial](https://www.sqlitetutorial.net/)

View file

@ -114,6 +114,7 @@ func (a *DBAdapter) CleanUp() {
} }
func (a *DBAdapter) Query(req Query) (*sql.Rows, error) { func (a *DBAdapter) Query(req Query) (*sql.Rows, error) {
fmt.Println(req)
db, err := a.Open(req.Db) db, err := a.Open(req.Db)
if err != nil { if err != nil {
return nil, err return nil, err
@ -132,6 +133,7 @@ func (a *DBAdapter) Query(req Query) (*sql.Rows, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return sfmt.Query(req.Data...) return sfmt.Query(req.Data...)
} }

View file

@ -116,12 +116,12 @@ func (b *Builder) Sort(sort Map) *Builder {
return b return b
} }
func (b *Builder) Limit(limit int) *Builder { func (b *Builder) Limit(limit int64) *Builder {
b.Parts.LimitExp = fmt.Sprintf("LIMIT %d", limit) b.Parts.LimitExp = fmt.Sprintf("LIMIT %d", limit)
return b return b
} }
func (b *Builder) Offset(offset int) *Builder { func (b *Builder) Offset(offset int64) *Builder {
b.Parts.OffsetExp = fmt.Sprintf("OFFSET %d", offset) b.Parts.OffsetExp = fmt.Sprintf("OFFSET %d", offset)
return b return b
} }