Skip to content

Commit 49d2930

Browse files
Merge pull request #3916 from drizzle-team/beta
Beta
2 parents 04c9143 + 9927cf1 commit 49d2930

File tree

17 files changed

+224
-21
lines changed

17 files changed

+224
-21
lines changed

changelogs/drizzle-kit/0.30.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix certificates generation utility for Drizzle Studio; [[BUG]: [drizzle-kit]: drizzle-kit dependency on drizzle-studio perms error](https://github.com/drizzle-team/drizzle-orm/issues/3729)

changelogs/drizzle-orm/0.29.5.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ await migrate(db, {
6767
});
6868
```
6969

70-
### 🎉 SQLite Proxy bacth and Relational Queries support
70+
### 🎉 SQLite Proxy batch and Relational Queries support
7171

7272
- You can now use `.query.findFirst` and `.query.findMany` syntax with sqlite proxy driver
7373

changelogs/drizzle-orm/0.38.4.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- New SingleStore type `vector` - thanks @mitchwadair
2+
- Fix wrong DROP INDEX statement generation, [#3866](https://github.com/drizzle-team/drizzle-orm/pull/3866) - thanks @WaciX
3+
- Typo fixes - thanks @stephan281094

drizzle-kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-kit",
3-
"version": "0.30.1",
3+
"version": "0.30.2",
44
"homepage": "https://orm.drizzle.team",
55
"keywords": [
66
"drizzle",

drizzle-kit/src/introspect-singlestore.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const singlestoreImportsList = new Set([
4949
'tinyint',
5050
'varbinary',
5151
'varchar',
52+
'vector',
5253
'year',
5354
'enum',
5455
]);
@@ -789,6 +790,16 @@ const column = (
789790
return out;
790791
}
791792

793+
if (lowered.startsWith('vector')) {
794+
const [dimensions, elementType] = lowered.substring('vector'.length + 1, lowered.length - 1).split(',');
795+
let out = `${casing(name)}: vector(${
796+
dbColumnName({ name, casing: rawCasing, withMode: true })
797+
}{ dimensions: ${dimensions}, elementType: ${elementType} })`;
798+
799+
out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : '';
800+
return out;
801+
}
802+
792803
console.log('uknown', type);
793804
return `// Warning: Can't parse ${type} from database\n\t// ${type}Type: ${type}("${name}")`;
794805
};

drizzle-kit/src/serializer/singlestoreSerializer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export const generateSingleStoreSnapshot = (
130130
if (typeof column.default === 'string') {
131131
columnToSet.default = `'${column.default}'`;
132132
} else {
133-
if (sqlTypeLowered === 'json') {
133+
if (sqlTypeLowered === 'json' || Array.isArray(column.default)) {
134134
columnToSet.default = `'${JSON.stringify(column.default)}'`;
135135
} else if (column.default instanceof Date) {
136136
if (sqlTypeLowered === 'date') {

drizzle-kit/src/sqlgenerator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3557,8 +3557,12 @@ class PgDropIndexConvertor extends Convertor {
35573557
}
35583558

35593559
convert(statement: JsonDropIndexStatement): string {
3560+
const { schema } = statement;
35603561
const { name } = PgSquasher.unsquashIdx(statement.data);
3561-
return `DROP INDEX "${name}";`;
3562+
3563+
const indexNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`;
3564+
3565+
return `DROP INDEX ${indexNameWithSchema};`;
35623566
}
35633567
}
35643568

drizzle-kit/src/utils/certs.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
import envPaths from 'env-paths';
22
import { mkdirSync } from 'fs';
33
import { access, readFile } from 'fs/promises';
4+
import { exec, ExecOptions } from 'node:child_process';
45
import { join } from 'path';
5-
import { $ } from 'zx';
66

7-
const p = envPaths('drizzle-studio', {
8-
suffix: '',
9-
});
10-
11-
$.verbose = false;
12-
$.cwd = p.data;
13-
mkdirSync(p.data, { recursive: true });
7+
export function runCommand(command: string, options: ExecOptions = {}) {
8+
return new Promise<{ exitCode: number }>((resolve) => {
9+
exec(command, options, (error) => {
10+
return resolve({ exitCode: error?.code ?? 0 });
11+
});
12+
});
13+
}
1414

1515
export const certs = async () => {
16-
const res = await $`mkcert --help`.nothrow();
17-
18-
// ~/.local/share/drizzle-studio
19-
const keyPath = join(p.data, 'localhost-key.pem');
20-
const certPath = join(p.data, 'localhost.pem');
16+
const res = await runCommand('mkcert --help');
2117

2218
if (res.exitCode === 0) {
19+
const p = envPaths('drizzle-studio', {
20+
suffix: '',
21+
});
22+
23+
// create ~/.local/share/drizzle-studio
24+
mkdirSync(p.data, { recursive: true });
25+
26+
// ~/.local/share/drizzle-studio
27+
const keyPath = join(p.data, 'localhost-key.pem');
28+
const certPath = join(p.data, 'localhost.pem');
29+
2330
try {
31+
// check if the files exist
2432
await Promise.all([access(keyPath), access(certPath)]);
2533
} catch (e) {
26-
await $`mkcert localhost`.nothrow();
34+
// if not create them
35+
await runCommand(`mkcert localhost`, { cwd: p.data });
2736
}
2837
const [key, cert] = await Promise.all([
2938
readFile(keyPath, { encoding: 'utf-8' }),
@@ -33,5 +42,3 @@ export const certs = async () => {
3342
}
3443
return null;
3544
};
36-
37-
certs();

drizzle-kit/tests/push/singlestore.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
tinyint,
2424
varbinary,
2525
varchar,
26+
vector,
2627
year,
2728
} from 'drizzle-orm/singlestore-core';
2829
import getPort from 'get-port';
@@ -249,6 +250,13 @@ const singlestoreSuite: DialectSuite = {
249250
columnNotNull: binary('column_not_null', { length: 1 }).notNull(),
250251
columnDefault: binary('column_default', { length: 12 }),
251252
}),
253+
254+
allVectors: singlestoreTable('all_vectors', {
255+
vectorSimple: vector('vector_simple', { dimensions: 1 }),
256+
vectorElementType: vector('vector_element_type', { dimensions: 1, elementType: 'I8' }),
257+
vectorNotNull: vector('vector_not_null', { dimensions: 1 }).notNull(),
258+
vectorDefault: vector('vector_default', { dimensions: 1 }).default([1]),
259+
}),
252260
};
253261

254262
const { statements } = await diffTestSchemasPushSingleStore(

drizzle-orm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-orm",
3-
"version": "0.38.3",
3+
"version": "0.38.4",
44
"description": "Drizzle ORM package for SQL databases",
55
"type": "module",
66
"scripts": {

drizzle-orm/src/singlestore-core/columns/all.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { timestamp } from './timestamp.ts';
2121
import { tinyint } from './tinyint.ts';
2222
import { varbinary } from './varbinary.ts';
2323
import { varchar } from './varchar.ts';
24+
import { vector } from './vector.ts';
2425
import { year } from './year.ts';
2526

2627
export function getSingleStoreColumnBuilders() {
@@ -51,6 +52,7 @@ export function getSingleStoreColumnBuilders() {
5152
tinyint,
5253
varbinary,
5354
varchar,
55+
vector,
5456
year,
5557
};
5658
}

drizzle-orm/src/singlestore-core/columns/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export * from './timestamp.ts';
2222
export * from './tinyint.ts';
2323
export * from './varbinary.ts';
2424
export * from './varchar.ts';
25+
export * from './vector.ts';
2526
export * from './year.ts';
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts';
2+
import type { ColumnBaseConfig } from '~/column.ts';
3+
import { entityKind } from '~/entity.ts';
4+
import type { AnySingleStoreTable } from '~/singlestore-core/table.ts';
5+
import { SQL } from '~/sql/index.ts';
6+
import { getColumnNameAndConfig } from '~/utils.ts';
7+
import { SingleStoreColumn, SingleStoreColumnBuilder, SingleStoreGeneratedColumnConfig } from './common.ts';
8+
9+
export type SingleStoreVectorBuilderInitial<TName extends string> = SingleStoreVectorBuilder<{
10+
name: TName;
11+
dataType: 'array';
12+
columnType: 'SingleStoreVector';
13+
data: Array<number>;
14+
driverParam: string;
15+
enumValues: undefined;
16+
}>;
17+
18+
export class SingleStoreVectorBuilder<T extends ColumnBuilderBaseConfig<'array', 'SingleStoreVector'>>
19+
extends SingleStoreColumnBuilder<T, SingleStoreVectorConfig>
20+
{
21+
static override readonly [entityKind]: string = 'SingleStoreVectorBuilder';
22+
23+
constructor(name: T['name'], config: SingleStoreVectorConfig) {
24+
super(name, 'array', 'SingleStoreVector');
25+
this.config.dimensions = config.dimensions;
26+
this.config.elementType = config.elementType;
27+
}
28+
29+
/** @internal */
30+
override build<TTableName extends string>(
31+
table: AnySingleStoreTable<{ name: TTableName }>,
32+
): SingleStoreVector<MakeColumnConfig<T, TTableName>> {
33+
return new SingleStoreVector<MakeColumnConfig<T, TTableName>>(
34+
table,
35+
this.config as ColumnBuilderRuntimeConfig<any, any>,
36+
);
37+
}
38+
39+
/** @internal */
40+
override generatedAlwaysAs(as: SQL<unknown> | (() => SQL) | T['data'], config?: SingleStoreGeneratedColumnConfig) {
41+
throw new Error('not implemented');
42+
}
43+
}
44+
45+
export class SingleStoreVector<T extends ColumnBaseConfig<'array', 'SingleStoreVector'>>
46+
extends SingleStoreColumn<T, SingleStoreVectorConfig>
47+
{
48+
static override readonly [entityKind]: string = 'SingleStoreVector';
49+
50+
dimensions: number = this.config.dimensions;
51+
elementType: ElementType | undefined = this.config.elementType;
52+
53+
getSQLType(): string {
54+
return `vector(${this.dimensions}, ${this.elementType || 'F32'})`;
55+
}
56+
57+
override mapToDriverValue(value: Array<number>) {
58+
return JSON.stringify(value);
59+
}
60+
61+
override mapFromDriverValue(value: string): Array<number> {
62+
return JSON.parse(value);
63+
}
64+
}
65+
66+
type ElementType = 'I8' | 'I16' | 'I32' | 'I64' | 'F32' | 'F64';
67+
68+
export interface SingleStoreVectorConfig {
69+
dimensions: number;
70+
elementType?: ElementType;
71+
}
72+
73+
export function vector(
74+
config: SingleStoreVectorConfig,
75+
): SingleStoreVectorBuilderInitial<''>;
76+
export function vector<TName extends string>(
77+
name: TName,
78+
config: SingleStoreVectorConfig,
79+
): SingleStoreVectorBuilderInitial<TName>;
80+
export function vector(a: string | SingleStoreVectorConfig, b?: SingleStoreVectorConfig) {
81+
const { name, config } = getColumnNameAndConfig<SingleStoreVectorConfig>(a, b);
82+
return new SingleStoreVectorBuilder(name, config);
83+
}

drizzle-orm/src/singlestore-core/expressions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ export function substring(
2323
chunks.push(sql`)`);
2424
return sql.join(chunks);
2525
}
26+
27+
// Vectors
28+
export function dotProduct(column: SingleStoreColumn | SQL.Aliased, value: Array<number>): SQL {
29+
return sql`${column} <*> ${JSON.stringify(value)}`;
30+
}
31+
32+
export function euclideanDistance(column: SingleStoreColumn | SQL.Aliased, value: Array<number>): SQL {
33+
return sql`${column} <-> ${JSON.stringify(value)}`;
34+
}

drizzle-orm/type-tests/singlestore/tables.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
uniqueIndex,
3535
varbinary,
3636
varchar,
37+
vector,
3738
year,
3839
} from '~/singlestore-core/index.ts';
3940
import { singlestoreSchema } from '~/singlestore-core/schema.ts';
@@ -917,6 +918,8 @@ Expect<
917918
varchar: varchar('varchar', { length: 1 }),
918919
varchar2: varchar('varchar2', { length: 1, enum: ['a', 'b', 'c'] }),
919920
varchardef: varchar('varchardef', { length: 1 }).default(''),
921+
vector: vector('vector', { dimensions: 1 }),
922+
vector2: vector('vector2', { dimensions: 1, elementType: 'I8' }),
920923
year: year('year'),
921924
yeardef: year('yeardef').default(0),
922925
});
@@ -1015,6 +1018,8 @@ Expect<
10151018
varchar: varchar({ length: 1 }),
10161019
varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }),
10171020
varchardef: varchar({ length: 1 }).default(''),
1021+
vector: vector({ dimensions: 1 }),
1022+
vector2: vector({ dimensions: 1, elementType: 'I8' }),
10181023
year: year(),
10191024
yeardef: year().default(0),
10201025
});

integration-tests/tests/singlestore/singlestore-common.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ import {
5858
uniqueIndex,
5959
uniqueKeyName,
6060
varchar,
61+
vector,
6162
year,
6263
} from 'drizzle-orm/singlestore-core';
64+
import { dotProduct, euclideanDistance } from 'drizzle-orm/singlestore-core/expressions';
6365
import { migrate } from 'drizzle-orm/singlestore/migrator';
6466
import getPort from 'get-port';
6567
import { v4 as uuid } from 'uuid';
@@ -156,6 +158,12 @@ const aggregateTable = singlestoreTable('aggregate_table', {
156158
nullOnly: int('null_only'),
157159
});
158160

161+
const vectorSearchTable = singlestoreTable('vector_search', {
162+
id: serial('id').notNull(),
163+
text: text('text').notNull(),
164+
embedding: vector('embedding', { dimensions: 10 }),
165+
});
166+
159167
// To test another schema and multischema
160168
const mySchema = singlestoreSchema(`mySchema`);
161169

@@ -366,6 +374,31 @@ export function tests(driver?: string) {
366374
]);
367375
}
368376

377+
async function setupVectorSearchTest(db: TestSingleStoreDB) {
378+
await db.execute(sql`drop table if exists \`vector_search\``);
379+
await db.execute(
380+
sql`
381+
create table \`vector_search\` (
382+
\`id\` integer primary key auto_increment not null,
383+
\`text\` text not null,
384+
\`embedding\` vector(10) not null
385+
)
386+
`,
387+
);
388+
await db.insert(vectorSearchTable).values([
389+
{
390+
id: 1,
391+
text: 'I like dogs',
392+
embedding: [0.6119, 0.1395, 0.2921, 0.3664, 0.4561, 0.7852, 0.1997, 0.5142, 0.5924, 0.0465],
393+
},
394+
{
395+
id: 2,
396+
text: 'I like cats',
397+
embedding: [0.6075, 0.1705, 0.0651, 0.9489, 0.9656, 0.8084, 0.3046, 0.0977, 0.6842, 0.4402],
398+
},
399+
]);
400+
}
401+
369402
test('table config: unsigned ints', async () => {
370403
const unsignedInts = singlestoreTable('cities1', {
371404
bigint: bigint('bigint', { mode: 'number', unsigned: true }),
@@ -2907,6 +2940,36 @@ export function tests(driver?: string) {
29072940
expect(result2[0]?.value).toBe(null);
29082941
});
29092942

2943+
test('simple vector search', async (ctx) => {
2944+
const { db } = ctx.singlestore;
2945+
const table = vectorSearchTable;
2946+
const embedding = [0.42, 0.93, 0.88, 0.57, 0.32, 0.64, 0.76, 0.52, 0.19, 0.81]; // ChatGPT's 10 dimension embedding for "dogs are cool" not sure how accurate but it works
2947+
await setupVectorSearchTest(db);
2948+
2949+
const withRankEuclidean = db.select({
2950+
id: table.id,
2951+
text: table.text,
2952+
rank: sql`row_number() over (order by ${euclideanDistance(table.embedding, embedding)})`.as('rank'),
2953+
}).from(table).as('with_rank');
2954+
const withRankDotProduct = db.select({
2955+
id: table.id,
2956+
text: table.text,
2957+
rank: sql`row_number() over (order by ${dotProduct(table.embedding, embedding)})`.as('rank'),
2958+
}).from(table).as('with_rank');
2959+
const result1 = await db.select({ id: withRankEuclidean.id, text: withRankEuclidean.text }).from(
2960+
withRankEuclidean,
2961+
).where(eq(withRankEuclidean.rank, 1));
2962+
const result2 = await db.select({ id: withRankDotProduct.id, text: withRankDotProduct.text }).from(
2963+
withRankDotProduct,
2964+
).where(eq(withRankDotProduct.rank, 1));
2965+
2966+
expect(result1.length).toEqual(1);
2967+
expect(result1[0]).toEqual({ id: 1, text: 'I like dogs' });
2968+
2969+
expect(result2.length).toEqual(1);
2970+
expect(result2[0]).toEqual({ id: 1, text: 'I like dogs' });
2971+
});
2972+
29102973
test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => {
29112974
const { db } = ctx.singlestore;
29122975

0 commit comments

Comments
 (0)