[feat] add query example
This commit is contained in:
parent
ae625a848d
commit
059fe4ca76
|
@ -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
|
||||||
|
|
|
@ -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))!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# bun-dal-exmaple
|
# bun-dal-example
|
||||||
|
|
||||||
To install dependencies:
|
To install dependencies:
|
||||||
|
|
||||||
|
|
50
examples/bun/find.test.ts
Normal file
50
examples/bun/find.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1 +0,0 @@
|
||||||
console.log("Hello via Bun!");
|
|
55
examples/bun/join.test.ts
Normal file
55
examples/bun/join.test.ts
Normal 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
5
examples/data/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
chinook.db-shm
|
||||||
|
chinook.db-wal
|
||||||
|
inserts.db
|
||||||
|
inserts.db-shm
|
||||||
|
inserts.db-wal
|
Binary file not shown.
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue