diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b30f8a6..802a2e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: latest + node-version: 22.13.1 - name: Setup Dependencies run: | corepack enable @@ -51,11 +51,11 @@ jobs: #setup node, bun and deno - uses: actions/setup-node@v4 with: - node-version: latest + node-version: 22.13.1 - uses: oven-sh/setup-bun@v2 - uses: denoland/setup-deno@v2 with: - deno-version: vx.x.x + deno-version: v2.x - name: setup playwright for browser related test run: npx playwright install --with-deps && npx playwright install msedge && npx playwright install chrome @@ -282,7 +282,7 @@ jobs: VITE_DATABASE_URL: ${{ secrets.CHINOOK_DATABASE_URL }} - name: deno with-javascript-vite - if: matrix.os != 'windows-latest' #https://github.com/denoland/deno/issues/23524#issuecomment-2292075726 + if: false #matrix.os != 'windows-latest' windows: https://github.com/denoland/deno/issues/23524#issuecomment-2292075726 linux-ubuntu: https://github.com/sqlitecloud/sqlitecloud-js/issues/197 working-directory: examples/with-javascript-vite run: deno add npm:@playwright/test && deno run --allow-all npm:playwright test env: diff --git a/bun.lockb b/bun.lockb index b73ae3f..62f289a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package-lock.json b/package-lock.json index d3b155c..fb5defe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,17 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.354", + "version": "1.0.400", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sqlitecloud/drivers", - "version": "1.0.354", + "version": "1.0.400", "license": "MIT", "dependencies": { - "@craftzdog/react-native-buffer": "^6.0.5", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "lz4js": "^0.2.0", - "react-native-url-polyfill": "^2.0.0", "socket.io-client": "^4.8.1", "whatwg-url": "^14.1.0" }, @@ -40,6 +38,10 @@ "engines": { "node": ">=18.0" }, + "optionalDependencies": { + "@craftzdog/react-native-buffer": "^6.0.5", + "react-native-url-polyfill": "^2.0.0" + }, "peerDependencies": { "react-native-quick-base64": "*", "react-native-tcp-socket": "^6.2.0" @@ -6946,6 +6948,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "ieee754": "^1.2.1", "react-native-quick-base64": "^2.0.5" @@ -14337,6 +14340,7 @@ "node_modules/react-native-url-polyfill": { "version": "2.0.0", "license": "MIT", + "optional": true, "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" }, @@ -16060,6 +16064,7 @@ "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "license": "MIT", + "optional": true, "dependencies": { "buffer": "^5.4.3", "punycode": "^2.1.1", @@ -16086,6 +16091,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -16094,6 +16100,7 @@ "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { "version": "5.0.0", "license": "BSD-2-Clause", + "optional": true, "engines": { "node": ">=8" } diff --git a/package.json b/package.json index b7965de..53c24cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.354", + "version": "1.0.400", "description": "SQLiteCloud drivers for Typescript/Javascript in edge, web and node clients", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -42,14 +42,16 @@ }, "homepage": "https://github.com/sqlitecloud/sqlitecloud-js#readme", "dependencies": { - "@craftzdog/react-native-buffer": "^6.0.5", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "lz4js": "^0.2.0", - "react-native-url-polyfill": "^2.0.0", "socket.io-client": "^4.8.1", "whatwg-url": "^14.1.0" }, + "optionalDependencies": { + "@craftzdog/react-native-buffer": "^6.0.5", + "react-native-url-polyfill": "^2.0.0" + }, "peerDependencies": { "react-native-quick-base64": "*", "react-native-tcp-socket": "^6.2.0" diff --git a/src/drivers/protocol.ts b/src/drivers/protocol.ts index 5ecd473..5d139b7 100644 --- a/src/drivers/protocol.ts +++ b/src/drivers/protocol.ts @@ -346,7 +346,7 @@ export function formatCommand(command: SQLiteCloudCommand): string { return serializeData(command.query, false) } -function serializeCommand(data: any[], zeroString: boolean = false): string { +function serializeCommand(data: SQLiteCloudDataTypes[], zeroString: boolean = false): string { const n = data.length let serializedData = `${n} ` @@ -356,11 +356,12 @@ function serializeCommand(data: any[], zeroString: boolean = false): string { serializedData += serializeData(data[i], zs) } - const header = `${CMD_ARRAY}${serializedData.length} ` + const bytesTotal = Buffer.byteLength(serializedData, 'utf-8') + const header = `${CMD_ARRAY}${bytesTotal} ` return header + serializedData } -function serializeData(data: any, zeroString: boolean = false): string { +function serializeData(data: SQLiteCloudDataTypes, zeroString: boolean = false): string { if (typeof data === 'string') { let cmd = CMD_STRING if (zeroString) { diff --git a/test/1brc.test.ts b/test/1brc.test.ts index 5e098bb..c03504c 100644 --- a/test/1brc.test.ts +++ b/test/1brc.test.ts @@ -78,7 +78,7 @@ async function createDatabaseAsync(numberOfRows: number): Promise<{ connection: const createSql = `UNUSE DATABASE; CREATE DATABASE ${database}; USE DATABASE ${database};` const createResults = await sendCommandsAsync(connection, createSql) expect(createResults).toBe('OK') - return { database, connection } + return { connection, database } } async function destroyDatabaseAsync(connection: SQLiteCloudConnection, database: string) { @@ -207,7 +207,7 @@ async function testChallenge(numberOfRows: number, insertChunks = BRC_INSERT_CHU throw error } } finally { - // await destroyDatabaseAsync(connection, database) + await destroyDatabaseAsync(connection, database) connection?.close() } } diff --git a/test/database.test.ts b/test/database.test.ts index a22bc90..b63d334 100644 --- a/test/database.test.ts +++ b/test/database.test.ts @@ -2,19 +2,10 @@ * database.test.ts - test driver api */ -import { SQLiteCloudRowset, SQLiteCloudRow, SQLiteCloudError, sanitizeSQLiteIdentifier } from '../src/index' -import { - getTestingDatabase, - getTestingDatabaseAsync, - getChinookDatabase, - removeDatabase, - removeDatabaseAsync, - LONG_TIMEOUT, - getChinookWebsocketConnection -} from './shared' +import { describe, expect, it } from '@jest/globals' import { RowCountCallback } from '../src/drivers/types' -import { expect, describe, it } from '@jest/globals' -import { Database } from 'sqlite3' +import { SQLiteCloudError, SQLiteCloudRow, SQLiteCloudRowset, sanitizeSQLiteIdentifier } from '../src/index' +import { LONG_TIMEOUT, getChinookDatabase, getTestingDatabase, getTestingDatabaseAsync, removeDatabase, removeDatabaseAsync } from './shared' // // utility methods to setup and destroy temporary test databases @@ -44,7 +35,6 @@ describe('Database.run', () => { expect(context.totalChanges).toBe(22) expect(context.finalized).toBe(1) - done() removeDatabase(database, error => { expect(error).toBeNull() done() @@ -103,7 +93,6 @@ describe('Database.run', () => { expect(context.totalChanges).toBe(22) expect(context.finalized).toBe(1) - done() removeDatabase(database, error => { expect(error).toBeNull() done() @@ -317,7 +306,7 @@ describe('Database.sql (async)', () => { const results = await database.sql('SELECT * FROM people WHERE name = ?', 'Emma Johnson') expect(results).toHaveLength(1) } finally { - database?.close() + await removeDatabaseAsync(database) } }) @@ -337,7 +326,7 @@ describe('Database.sql (async)', () => { hobby: 'Collecting clouds' }) } finally { - database?.close() + await removeDatabaseAsync(database) } }) @@ -487,56 +476,98 @@ describe('Database.sql (async)', () => { describe('should sanitize identifiers', () => { it('should sanitize database name and run the query', async () => { - const database = await getTestingDatabaseAsync() + let database + try { + database = await getTestingDatabaseAsync() - const databaseName = sanitizeSQLiteIdentifier(database.getConfiguration().database || '') - await expect(database.sql(`USE DATABASE ${databaseName}`)).resolves.toBe('OK') + const databaseName = sanitizeSQLiteIdentifier(database.getConfiguration().database || '') + await expect(database.sql(`USE DATABASE ${databaseName}`)).resolves.toBe('OK') + } finally { + await removeDatabaseAsync(database) + } }) it('should sanitize table name and run the query', async () => { - const database = await getTestingDatabaseAsync() + let database + try { + database = await getTestingDatabaseAsync() - const table = sanitizeSQLiteIdentifier('people') - await expect(database.sql(`SELECT id FROM ${table} LIMIT 1`)).resolves.toMatchObject([{ id: 1 }]) + const table = sanitizeSQLiteIdentifier('people') + await expect(database.sql(`SELECT id FROM ${table} LIMIT 1`)).resolves.toMatchObject([{ id: 1 }]) + } finally { + await removeDatabaseAsync(database) + } }) it('should sanitize SQL Injection as table name', async () => { - const database = await getTestingDatabaseAsync() - const databaseName = database.getConfiguration().database + let database + try { + database = await getTestingDatabaseAsync() + const databaseName = database.getConfiguration().database - const sanitizedDBName = sanitizeSQLiteIdentifier(`${databaseName}; SELECT * FROM people; -- `) - await expect(database.sql(`USE DATABASE ${sanitizedDBName}`)).rejects.toThrow( - `Database name contains invalid characters (${databaseName}; SELECT * FROM people; --).` - ) + const sanitizedDBName = sanitizeSQLiteIdentifier(`${databaseName}; SELECT * FROM people; -- `) + await expect(database.sql(`USE DATABASE ${sanitizedDBName}`)).rejects.toThrow( + `Database name contains invalid characters (${databaseName}; SELECT * FROM people; --).` + ) - const table = sanitizeSQLiteIdentifier('people; -- ') - await expect(database.sql(`SELECT * FROM ${table} WHERE people = 1`)).rejects.toThrow('no such table: people; --') + const table = sanitizeSQLiteIdentifier('people; -- ') + await expect(database.sql(`SELECT * FROM ${table} WHERE people = 1`)).rejects.toThrow('no such table: people; --') + } finally { + await removeDatabaseAsync(database) + } }) }) it('should throw exception when using table name as binding', async () => { - const database = await getTestingDatabaseAsync() - const table = 'people' - await expect(database.sql`SELECT * FROM ${table}`).rejects.toThrow('near "?": syntax error') + let database + try { + database = await getTestingDatabaseAsync() + const table = 'people' + await expect(database.sql`SELECT * FROM ${table}`).rejects.toThrow('near "?": syntax error') + } finally { + await removeDatabaseAsync(database) + } }) - it('should built in commands accept bindings', async () => { - const database = await getTestingDatabaseAsync() + it('should commands accept bindings', async () => { + let database + try { + database = await getTestingDatabaseAsync() + + const databaseName = database.getConfiguration().database || '' + await expect(database.sql`USE DATABASE ${databaseName}`).resolves.toBe('OK') + + const databaseNameInjectSQL = `${databaseName}; SELECT * FROM people` + await expect(database.sql`USE DATABASE ${databaseNameInjectSQL}`).rejects.toThrow(`Database name contains invalid characters (${databaseNameInjectSQL}).`) + + let key = 'logo_level' + let value = 'debug' + await expect(database.sql`SET KEY ${key} TO ${value}`).resolves.toBe('OK') - const databaseName = database.getConfiguration().database || '' - await expect(database.sql`USE DATABASE ${databaseName}`).resolves.toBe('OK') + key = 'logo_level' + value = 'debug; DROP TABLE people' + await expect(database.sql`SET KEY ${key} TO ${value}`).resolves.toBe('OK') + const result = await database.sql`SELECT * FROM people` + expect(result.length).toBeGreaterThan(0) + } finally { + await removeDatabaseAsync(database) + } + }) - const databaseNameInjectSQL = `${databaseName}; SELECT * FROM people` - await expect(database.sql`USE DATABASE ${databaseNameInjectSQL}`).rejects.toThrow(`Database name contains invalid characters (${databaseNameInjectSQL}).`) + it('binding should work with unicode character', async () => { + let database + try { + database = await getTestingDatabaseAsync() + const name = 'unicorn-🦄' - let key = 'logo_level' - let value = 'debug' - await expect(database.sql`SET KEY ${key} TO ${value}`).resolves.toBe('OK') + let results = await database.sql('INSERT INTO people (name, age, hobby) VALUES (?, 11, "");', name) + expect(results.changes).toEqual(1) - key = 'logo_level' - value = 'debug; DROP TABLE people' - await expect(database.sql`SET KEY ${key} TO ${value}`).resolves.toBe('OK') - const result = await database.sql`SELECT * FROM people` - expect(result.length).toBeGreaterThan(0) + results = await database.sql('SELECT * FROM people WHERE name = ?;', name) + expect(results).toHaveLength(1) + expect(results[0].name).toEqual(name) + } finally { + await removeDatabaseAsync(database) + } }) }) diff --git a/test/shared.ts b/test/shared.ts index ace81ef..8e5d7ae 100644 --- a/test/shared.ts +++ b/test/shared.ts @@ -25,9 +25,9 @@ export const WARN_SPEED_MS = 500 export const EXPECT_SPEED_MS = 6 * 1000 /** Number of times or size of stress (when repeated in sequence) */ -export const SEQUENCE_TEST_SIZE = 150 +export const SEQUENCE_TEST_SIZE = 90 /** Concurrency size for multiple connection tests */ -export const SIMULTANEOUS_TEST_SIZE = 150 +export const SIMULTANEOUS_TEST_SIZE = 90 /** Testing database from .env file */ export const CHINOOK_DATABASE_URL = process.env.CHINOOK_DATABASE_URL as string diff --git a/test/stress.test.ts b/test/stress.test.ts index a28c200..7b8c9a5 100644 --- a/test/stress.test.ts +++ b/test/stress.test.ts @@ -14,7 +14,7 @@ import { EXPECT_SPEED_MS } from './shared' -describe('stress testing', () => { +describe.skip('stress testing', () => { it( 'should do lots of read connections in sequence', async () => {