Skip to content

Commit 28e9b8c

Browse files
authored
Remove sequelize (#3012)
* First commit Signed-off-by: Prabhu Subramanian <[email protected]> * Better compatibility with existing database Signed-off-by: Prabhu Subramanian <[email protected]> * db unit tests Signed-off-by: Prabhu Subramanian <[email protected]> * fix binary builds Signed-off-by: Prabhu Subramanian <[email protected]> --------- Signed-off-by: Prabhu Subramanian <[email protected]>
1 parent be829ef commit 28e9b8c

File tree

8 files changed

+319
-342
lines changed

8 files changed

+319
-342
lines changed

.github/workflows/binary-builds.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
# Set all the runners
5858
- os: darwin
5959
arch: amd64
60-
runner: macos-14-large
60+
runner: macos-15-intel
6161
- os: darwin
6262
arch: arm64
6363
runner: macos-14

lib/evinser/evinser.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import path, { basename, join, resolve } from "node:path";
33
import process from "node:process";
44

55
import { PackageURL } from "packageurl-js";
6-
import { Op } from "sequelize";
76

87
import { findCryptoAlgos } from "../helpers/cbomutils.js";
98
import * as db from "../helpers/db.js";
@@ -908,7 +907,7 @@ export async function parseSliceUsages(
908907
attributes: ["purl"],
909908
where: {
910909
data: {
911-
[Op.like]: `%${atype}%`,
910+
like: `%${atype}%`,
912911
},
913912
},
914913
});
@@ -1654,7 +1653,7 @@ export async function collectDataFlowFrames(
16541653
attributes: ["purl"],
16551654
where: {
16561655
data: {
1657-
[Op.like]: `%${typeFullName}%`,
1656+
like: `%${typeFullName}%`,
16581657
},
16591658
},
16601659
});

lib/helpers/db.js

Lines changed: 150 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,160 @@
11
import path from "node:path";
22

3-
import { DataTypes, Model, Sequelize } from "sequelize";
4-
import SQLite from "sqlite3";
3+
import sqlite3 from "sqlite3";
54

6-
class Namespaces extends Model {}
7-
class Usages extends Model {}
8-
class DataFlows extends Model {}
5+
const {
6+
Database,
7+
OPEN_READWRITE,
8+
OPEN_CREATE,
9+
OPEN_NOMUTEX,
10+
OPEN_SHAREDCACHE,
11+
} = sqlite3;
12+
13+
/**
14+
* A lightweight Model wrapper to mimic Sequelize behavior using raw sqlite3
15+
*/
16+
class Model {
17+
constructor(db, tableName) {
18+
this.db = db;
19+
this.tableName = tableName;
20+
}
21+
22+
/**
23+
* Initialize table
24+
*/
25+
async init() {
26+
const sql = `CREATE TABLE IF NOT EXISTS ${this.tableName} (
27+
purl TEXT PRIMARY KEY,
28+
data JSON NOT NULL,
29+
createdAt DATETIME NOT NULL,
30+
updatedAt DATETIME NOT NULL
31+
)`;
32+
return new Promise((resolve, reject) => {
33+
this.db.run(sql, (err) => {
34+
if (err) reject(err);
35+
else resolve();
36+
});
37+
});
38+
}
39+
40+
/**
41+
* findByPk
42+
* Returns null if not found, or an object { purl, data (parsed object) }
43+
*/
44+
async findByPk(purl) {
45+
const sql = `SELECT * FROM ${this.tableName} WHERE purl = ?`;
46+
return new Promise((resolve, reject) => {
47+
this.db.get(sql, [purl], (err, row) => {
48+
if (err) {
49+
reject(err);
50+
} else if (!row) {
51+
resolve(null);
52+
} else {
53+
try {
54+
row.data = JSON.parse(row.data);
55+
} catch (_e) {
56+
// ignore
57+
}
58+
resolve(row);
59+
}
60+
});
61+
});
62+
}
63+
64+
/**
65+
* findOrCreate
66+
* @param {Object} options { where: { purl }, defaults: { purl, data } }
67+
*/
68+
async findOrCreate(options) {
69+
const { where, defaults } = options;
70+
const existing = await this.findByPk(where.purl);
71+
72+
if (existing) {
73+
return [existing, false];
74+
}
75+
76+
const insertSql = `INSERT INTO ${this.tableName} (purl, data, createdAt, updatedAt) VALUES (?, ?, ?, ?)`;
77+
const dataStr =
78+
typeof defaults.data === "string"
79+
? defaults.data
80+
: JSON.stringify(defaults.data);
81+
const now = new Date().toISOString();
82+
return new Promise((resolve, reject) => {
83+
this.db.run(insertSql, [defaults.purl, dataStr, now, now], (err) => {
84+
if (err) reject(err);
85+
else {
86+
const instance = {
87+
purl: defaults.purl,
88+
data: defaults.data,
89+
createdAt: now,
90+
updatedAt: now,
91+
};
92+
resolve([instance, true]);
93+
}
94+
});
95+
});
96+
}
97+
98+
/**
99+
* findAll to handle the specific LIKE query from evinser.js
100+
* @param {Object} options
101+
*/
102+
async findAll(options) {
103+
let sql = `SELECT * FROM ${this.tableName}`;
104+
const params = [];
105+
106+
if (options?.where?.data) {
107+
if (options.where.data.like) {
108+
sql += " WHERE data LIKE ?";
109+
params.push(options.where.data.like);
110+
}
111+
}
112+
113+
return new Promise((resolve, reject) => {
114+
this.db.all(sql, params, (err, rows) => {
115+
if (err) {
116+
reject(err);
117+
} else {
118+
const results = rows.map((r) => {
119+
try {
120+
r.data = JSON.parse(r.data);
121+
} catch (_e) {
122+
// ignore
123+
}
124+
return r;
125+
});
126+
resolve(results);
127+
}
128+
});
129+
});
130+
}
131+
}
9132

10133
export const createOrLoad = async (dbName, dbPath, logging = false) => {
11-
const sequelize = new Sequelize({
12-
define: {
13-
freezeTableName: true,
14-
},
15-
dialect: "sqlite",
16-
dialectOptions: {
17-
mode:
18-
SQLite.OPEN_READWRITE |
19-
SQLite.OPEN_CREATE |
20-
SQLite.OPEN_NOMUTEX |
21-
SQLite.OPEN_SHAREDCACHE,
22-
},
23-
storage: dbPath.includes("memory") ? dbPath : path.join(dbPath, dbName),
24-
logging,
25-
pool: {
26-
max: 5,
27-
min: 0,
28-
acquire: 30000,
29-
idle: 10000,
30-
},
134+
const fullPath = dbPath.includes("memory")
135+
? dbPath
136+
: path.join(dbPath, dbName);
137+
138+
const mode = OPEN_READWRITE | OPEN_CREATE | OPEN_NOMUTEX | OPEN_SHAREDCACHE;
139+
140+
const db = new Database(fullPath, mode, (err) => {
141+
if (err && logging) console.error(err.message);
31142
});
32-
Namespaces.init(
33-
{
34-
purl: {
35-
type: DataTypes.STRING,
36-
allowNull: false,
37-
primaryKey: true,
38-
},
39-
data: {
40-
type: DataTypes.JSON,
41-
allowNull: false,
42-
},
43-
},
44-
{ sequelize, modelName: "Namespaces" },
45-
);
46-
Usages.init(
47-
{
48-
purl: {
49-
type: DataTypes.STRING,
50-
allowNull: false,
51-
primaryKey: true,
52-
},
53-
data: {
54-
type: DataTypes.JSON,
55-
allowNull: false,
56-
},
57-
},
58-
{ sequelize, modelName: "Usages" },
59-
);
60-
DataFlows.init(
61-
{
62-
purl: {
63-
type: DataTypes.STRING,
64-
allowNull: false,
65-
primaryKey: true,
66-
},
67-
data: {
68-
type: DataTypes.JSON,
69-
allowNull: false,
70-
},
71-
},
72-
{ sequelize, modelName: "DataFlows" },
73-
);
74-
await sequelize.sync();
143+
144+
if (logging) {
145+
db.on("trace", (sql) => console.log(`[sqlite] ${sql}`));
146+
}
147+
148+
const Namespaces = new Model(db, "Namespaces");
149+
const Usages = new Model(db, "Usages");
150+
const DataFlows = new Model(db, "DataFlows");
151+
152+
await Namespaces.init();
153+
await Usages.init();
154+
await DataFlows.init();
155+
75156
return {
76-
sequelize,
157+
sequelize: db,
77158
Namespaces,
78159
Usages,
79160
DataFlows,

lib/helpers/db.poku.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { assert, describe, test } from "poku";
2+
3+
import { createOrLoad } from "./db.js";
4+
5+
describe("SQLite3 Helper Tests", async () => {
6+
const { sequelize, Namespaces } = await createOrLoad(
7+
"test.db",
8+
":memory:",
9+
false,
10+
);
11+
12+
await test("Model Initialization", () => {
13+
assert.ok(sequelize, "Database instance should exist");
14+
assert.ok(Namespaces, "Namespaces model should be initialized");
15+
});
16+
17+
await test("Namespaces: findOrCreate (New Record)", async () => {
18+
const purl = "pkg:npm/[email protected]";
19+
const data = {
20+
description: "Promise based HTTP client",
21+
versions: ["1.0.0"],
22+
};
23+
24+
const [instance, created] = await Namespaces.findOrCreate({
25+
where: { purl },
26+
defaults: { purl, data },
27+
});
28+
29+
assert.strictEqual(created, true, "Should indicate record was created");
30+
assert.strictEqual(instance.purl, purl, "PURL should match");
31+
assert.deepStrictEqual(instance.data, data, "Data object should match");
32+
assert.ok(instance.createdAt, "createdAt should exist");
33+
assert.ok(instance.updatedAt, "updatedAt should exist");
34+
});
35+
36+
await test("Namespaces: findOrCreate (Existing Record)", async () => {
37+
const purl = "pkg:npm/[email protected]";
38+
const data = { ignored: "data" };
39+
40+
const [instance, created] = await Namespaces.findOrCreate({
41+
where: { purl },
42+
defaults: { purl, data },
43+
});
44+
45+
assert.strictEqual(
46+
created,
47+
false,
48+
"Should indicate record was NOT created",
49+
);
50+
assert.strictEqual(instance.purl, purl, "PURL should match existing");
51+
assert.notDeepStrictEqual(
52+
instance.data,
53+
data,
54+
"Should return existing data from DB, not new defaults",
55+
);
56+
assert.strictEqual(
57+
instance.data.description,
58+
"Promise based HTTP client",
59+
"Data integrity check",
60+
);
61+
});
62+
63+
await test("Namespaces: findByPk", async () => {
64+
const purl = "pkg:npm/[email protected]";
65+
const record = await Namespaces.findByPk(purl);
66+
67+
assert.ok(record, "Record should be found");
68+
assert.strictEqual(record.purl, purl);
69+
assert.strictEqual(
70+
typeof record.data,
71+
"object",
72+
"Data string should be parsed back to JSON object",
73+
);
74+
assert.strictEqual(
75+
record.data.versions[0],
76+
"1.0.0",
77+
"Nested JSON data should be accessible",
78+
);
79+
});
80+
81+
await test("Namespaces: findByPk (Not Found)", async () => {
82+
const record = await Namespaces.findByPk("pkg:npm/non-existent");
83+
assert.strictEqual(
84+
record,
85+
null,
86+
"Should return null for non-existent records",
87+
);
88+
});
89+
90+
await test("Namespaces: findAll (with LIKE filter)", async () => {
91+
await Namespaces.findOrCreate({
92+
where: { purl: "pkg:npm/react" },
93+
defaults: {
94+
purl: "pkg:npm/react",
95+
data: { description: "UI Library", author: "Meta" },
96+
},
97+
});
98+
99+
const results = await Namespaces.findAll({
100+
where: {
101+
data: { like: "%HTTP%" },
102+
},
103+
});
104+
105+
assert.strictEqual(
106+
results.length,
107+
1,
108+
"Should find exactly one record matching 'HTTP'",
109+
);
110+
assert.strictEqual(results[0].purl, "pkg:npm/[email protected]");
111+
});
112+
113+
await test("Namespaces: findAll (No Filter)", async () => {
114+
const results = await Namespaces.findAll({});
115+
assert.strictEqual(
116+
results.length,
117+
2,
118+
"Should return all records in the table",
119+
);
120+
});
121+
122+
sequelize.close();
123+
});

0 commit comments

Comments
 (0)