[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 (
"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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
# bun-dal-exmaple
# bun-dal-example
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);
});
});

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

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

View file

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