Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redisqlite #7

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,36 @@ async function main(args) {
// Database (MySQL)
const db = await nim.mysql(); // Returns a configured mysql2 connection.
const [rows, fields] = await db.execute('SELECT * FROM `table`');

// Embedded Database (Sqlite)
const sql = nim.sqlite()
// execute a statement
// it returns [lastId, changedRows] where relevant
let res = await sql.exec("create table t(i int)")
// execute a parametric statement with parameters
res = await sql.exec(["insert into t(i) values(?)",1])
// execute a query, returns an array of objects
// each object corresponds to a record: [{i:1},{i:2}]
let m = await sql.map("select * from t")
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
// you can also pass parameters
// and limit the number of returned elements
m = await sql.map(["select * from t where i >?",],1) // [{i:1}]
// execute a query, returns an array of arrays
// each array corresponds to record values: [[1],[2]]
let m = await sql.map("select * from t")
// you can also pass parameters
// and limit the number of returned elements
m = await sql.map(["select * from t where i >?",],1) // [[1]]
// you can prepare statements
let ins = await sql.prep("insert into t(i) values(?)")
let sel = await sql.prep("select * from t where i>?")
// the returned value is a number and can be used to execute
res = await sql.exec([ins,1])
m = await sql.map([sel,1],1)
// when you do not need any more close the statement
// running prep again with the returned value
await sql.prep(ins)
await sql.prep(sel)
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
}
```

Expand Down
12 changes: 10 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
var redis = require('redis');
var bluebird = require('bluebird');
const { getStorageProvider } = require('@nimbella/storage');
var Redisqlite = require("./redisqlite.js")
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved

function makeRedisClient() {
bluebird.promisifyAll(redis.RedisClient.prototype);
Expand All @@ -29,7 +30,7 @@ function makeRedisClient() {
if (!redisPassword || redisPassword.length == 0) {
throw new Error('Key-Value store password is not available');
}
const redisParam = {port: 6379, host: redisHost};
const redisParam = { port: 6379, host: redisHost };
const client = redis.createClient(redisParam);
if (client == null) {
throw new Error('Error creating redis client');
Expand Down Expand Up @@ -96,11 +97,18 @@ async function makeSqlClient() {
});
}


function makeSqliteClient() {
let client = makeRedisClient()
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
return new Redisqlite(client)
}

module.exports = {
redis: makeRedisClient,
// Legacy function, returns Promise<Bucket>
storage: legacyMakeStorageClient,
// New version of the function, returns the more abstract type StorageClient
storageClient: makeStorageClient,
mysql: makeSqlClient
mysql: makeSqlClient,
sqlite: makeSqliteClient
};
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@
"bluebird": "^3.7.2",
"mysql2": "^2.1.0",
"redis": "^3.0.2"
},
"scripts": {
"start": "docker run -d -p 6379:6379 --rm --name redisqlite sciabarracom/redisqlite:1.0.2 --requirepass password",
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
"test": "npm run start ; jest ; npm run stop",
"stop": "docker kill redisqlite"
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
}
}
87 changes: 87 additions & 0 deletions redisqlite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2020-present, Nimbella, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

function Redisqlite(redis) {
this.redis = redis
}

Redisqlite.prototype.exec = function (sql) {
if (!Array.isArray(sql))
sql = [sql]
var redis = this.redis
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
sciabarracom marked this conversation as resolved.
Show resolved Hide resolved
return new Promise(function (resolve, reject) {
redis.send_command("SQLEXEC", sql,
function (err, res) {
if (err)
reject(err)
else
resolve(res.map(JSON.parse))
})
})
}

Redisqlite.prototype.prep = function (sql) {
var redis = this.redis
if (!Array.isArray(sql))
sql = [sql]
return new Promise(function (resolve, reject) {
redis.send_command("SQLPREP", sql,
function (err, res) {
if (err)
reject(err)
else
resolve(res)
})
})
}

Redisqlite.prototype.map = function (sql, count) {
if(count === undefined)
count = 0
if (!Array.isArray(sql))
sql = [sql]
sql.unshift(count)
var redis = this.redis
return new Promise(function (resolve, reject) {
redis.send_command("SQLMAP", sql,
function (err, res) {
if (err)
reject(err)
else
resolve(res.map(JSON.parse))
})
})
}

Redisqlite.prototype.list = function (sql, count) {
if(count === undefined)
count = 0
if (!Array.isArray(sql))
sql = [sql]
sql.unshift(count)
var redis = this.redis
return new Promise(function (resolve, reject) {
redis.send_command("SQLARR", sql,
function (err, res) {
if (err)
reject(err)
else
resolve(res.map(JSON.parse))
})
})
}

module.exports = Redisqlite
132 changes: 132 additions & 0 deletions redisqlite.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Copyright (c) 2020-present, Nimbella, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var sql

beforeAll(async () => {
process.env['__NIM_REDIS_IP'] = '127.0.0.1'
process.env['__NIM_REDIS_PASSWORD'] = 'password'
sql = require('./index.js').sqlite()
})

beforeEach(async () => {
sql.exec("drop table t").catch(x => true)
})

afterAll(async() => {
await sql.redis.quitAsync()
})

test("basic", async () => {

let res = await sql.exec("create table t(i int)")
//console.log(res)
expect(res.length).toBe(2)

let ins = await sql.exec("insert into t(i) values(1),(2),(3)")
//console.log(ins[0],typeof(ins[0]), ins[1], typeof(ins[1]))
expect(ins).toStrictEqual([3,3])

let m = await sql.map("select * from t")
//console.log(m)
expect(m).toStrictEqual([{i:1},{i:2},{i:3}])

let m1 = await sql.map("select * from t", 1)
//console.log(m)
expect(m1).toStrictEqual([{i:1}])

let m2 = await sql.map("select * from t", 2)
//console.log(m)
expect(m2).toStrictEqual([{i:1},{i:2}])

let a = await sql.list("select * from t")
//console.log(a)
expect(a).toStrictEqual([[1],[2],[3]])

let a1 = await sql.list("select * from t",1)
//console.log(a)
expect(a1).toStrictEqual([[1]])

let a2 = await sql.list("select * from t",2)
//console.log(a)
expect(a2).toStrictEqual([[1],[2]])

})

test("with args", async () => {

let res = await sql.exec("create table t(i int)")
//console.log(res)
expect(res.length).toBe(2)

let ins = await sql.exec(["insert into t(i) values(?),(?),(?)",1,2,3])
//console.log(ins[0],typeof(ins[0]), ins[1], typeof(ins[1]))
expect(ins).toStrictEqual([3,3])

let m = await sql.map(["select * from t where i>?",1])
//console.log(m)
expect(m).toStrictEqual([{i:2},{i:3}])

let m1 = await sql.map(["select * from t where i>?",1],1)
//console.log(m)
expect(m1).toStrictEqual([{i:2}])

let a = await sql.list(["select * from t where i<?",3])
//console.log(a)
expect(a).toStrictEqual([[1],[2]])

let a1 = await sql.list(["select * from t where i<?",3],1)
//console.log(a)
expect(a1).toStrictEqual([[1]])
})

test("prepared", async () => {

await sql.exec("create table t(i int, s varchar)")

let sel = await sql.prep("select s from t where i <?")
//console.log(ins)
expect(typeof sel).toBe('number')

let ins = await sql.prep("insert into t(i, s) values(?,?)")
//console.log(ins)
expect(typeof ins).toBe('number')

await sql.exec([ins, 1, 'a'])
await sql.exec([ins, 2, 'b'])
await sql.exec([ins, 3, 'c'])

let m = await sql.map([sel, 3])
//console.log(m)
expect(m).toStrictEqual([ { s: 'a' }, { s: 'b' } ])

let a = await sql.list([sel, 3],1)
//console.log(a)
expect(a).toStrictEqual([ [ 'a' ] ])

// todo unprep
let ok = await sql.prep(sel)
expect(ok).toBe('OK')
await sql.prep(ins)
sql.prep(sel).catch(e => expect(e.message).toBe('invalid prepared statement index'))
})

test("errors", async() => {
sql.exec("xxx").catch(e => expect(e.message).toBe('near "xxx": syntax error'))
sql.prep("xxx").catch(e => expect(e.message).toBe('near "xxx": syntax error'))
sql.map("xxx").catch(e => expect(e.message).toBe('near "xxx": syntax error'))
sql.list("xxx").catch(e => expect(e.message).toBe('near "xxx": syntax error'))
})