@mastra/pg 0.2.10-alpha.4 → 0.2.10-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +14 -0
- package/LICENSE.md +4 -43
- package/dist/_tsup-dts-rollup.d.cts +17 -1
- package/dist/_tsup-dts-rollup.d.ts +17 -1
- package/dist/index.cjs +154 -39
- package/dist/index.js +154 -39
- package/package.json +3 -3
- package/src/storage/index.test.ts +277 -12
- package/src/storage/index.ts +90 -21
- package/src/vector/index.test.ts +483 -0
- package/src/vector/index.ts +101 -21
package/src/vector/index.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QueryResult } from '@mastra/core';
|
|
2
|
+
import * as pg from 'pg';
|
|
2
3
|
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
|
|
3
4
|
|
|
4
5
|
import { PgVector } from '.';
|
|
@@ -1858,4 +1859,486 @@ describe('PgVector', () => {
|
|
|
1858
1859
|
await vectorDB.deleteIndex(indexName);
|
|
1859
1860
|
});
|
|
1860
1861
|
});
|
|
1862
|
+
|
|
1863
|
+
describe('Schema Support', () => {
|
|
1864
|
+
const customSchema = 'mastra_test';
|
|
1865
|
+
let vectorDB: PgVector;
|
|
1866
|
+
let customSchemaVectorDB: PgVector;
|
|
1867
|
+
|
|
1868
|
+
beforeAll(async () => {
|
|
1869
|
+
// Initialize default vectorDB first
|
|
1870
|
+
vectorDB = new PgVector(connectionString);
|
|
1871
|
+
|
|
1872
|
+
// Create schema using the default vectorDB connection
|
|
1873
|
+
const client = await vectorDB['pool'].connect();
|
|
1874
|
+
try {
|
|
1875
|
+
await client.query(`CREATE SCHEMA IF NOT EXISTS ${customSchema}`);
|
|
1876
|
+
await client.query('COMMIT');
|
|
1877
|
+
} catch (e) {
|
|
1878
|
+
await client.query('ROLLBACK');
|
|
1879
|
+
throw e;
|
|
1880
|
+
} finally {
|
|
1881
|
+
client.release();
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// Now create the custom schema vectorDB instance
|
|
1885
|
+
customSchemaVectorDB = new PgVector({
|
|
1886
|
+
connectionString,
|
|
1887
|
+
schemaName: customSchema,
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
afterAll(async () => {
|
|
1892
|
+
// Clean up test tables and schema
|
|
1893
|
+
try {
|
|
1894
|
+
await customSchemaVectorDB.deleteIndex('schema_test_vectors');
|
|
1895
|
+
} catch {
|
|
1896
|
+
// Ignore errors if index doesn't exist
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// Drop schema using the default vectorDB connection
|
|
1900
|
+
const client = await vectorDB['pool'].connect();
|
|
1901
|
+
try {
|
|
1902
|
+
await client.query(`DROP SCHEMA IF EXISTS ${customSchema} CASCADE`);
|
|
1903
|
+
await client.query('COMMIT');
|
|
1904
|
+
} catch (e) {
|
|
1905
|
+
await client.query('ROLLBACK');
|
|
1906
|
+
throw e;
|
|
1907
|
+
} finally {
|
|
1908
|
+
client.release();
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// Disconnect in reverse order
|
|
1912
|
+
await customSchemaVectorDB.disconnect();
|
|
1913
|
+
await vectorDB.disconnect();
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
describe('Constructor', () => {
|
|
1917
|
+
it('should accept connectionString directly', () => {
|
|
1918
|
+
const db = new PgVector(connectionString);
|
|
1919
|
+
expect(db).toBeInstanceOf(PgVector);
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
it('should accept config object with connectionString', () => {
|
|
1923
|
+
const db = new PgVector({ connectionString });
|
|
1924
|
+
expect(db).toBeInstanceOf(PgVector);
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1927
|
+
it('should accept config object with schema', () => {
|
|
1928
|
+
const db = new PgVector({ connectionString, schemaName: customSchema });
|
|
1929
|
+
expect(db).toBeInstanceOf(PgVector);
|
|
1930
|
+
});
|
|
1931
|
+
});
|
|
1932
|
+
|
|
1933
|
+
describe('Schema Operations', () => {
|
|
1934
|
+
const testIndexName = 'schema_test_vectors';
|
|
1935
|
+
|
|
1936
|
+
beforeEach(async () => {
|
|
1937
|
+
// Clean up any existing indexes
|
|
1938
|
+
try {
|
|
1939
|
+
await customSchemaVectorDB.deleteIndex(testIndexName);
|
|
1940
|
+
} catch {
|
|
1941
|
+
// Ignore if doesn't exist
|
|
1942
|
+
}
|
|
1943
|
+
try {
|
|
1944
|
+
await vectorDB.deleteIndex(testIndexName);
|
|
1945
|
+
} catch {
|
|
1946
|
+
// Ignore if doesn't exist
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
|
|
1950
|
+
afterEach(async () => {
|
|
1951
|
+
// Clean up indexes after each test
|
|
1952
|
+
try {
|
|
1953
|
+
await customSchemaVectorDB.deleteIndex(testIndexName);
|
|
1954
|
+
} catch {
|
|
1955
|
+
// Ignore if doesn't exist
|
|
1956
|
+
}
|
|
1957
|
+
try {
|
|
1958
|
+
await vectorDB.deleteIndex(testIndexName);
|
|
1959
|
+
} catch {
|
|
1960
|
+
// Ignore if doesn't exist
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
it('should create and query index in custom schema', async () => {
|
|
1965
|
+
// Create index in custom schema
|
|
1966
|
+
await customSchemaVectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
1967
|
+
|
|
1968
|
+
// Insert test vectors
|
|
1969
|
+
const vectors = [
|
|
1970
|
+
[1, 2, 3],
|
|
1971
|
+
[4, 5, 6],
|
|
1972
|
+
];
|
|
1973
|
+
const metadata = [{ test: 'custom_schema_1' }, { test: 'custom_schema_2' }];
|
|
1974
|
+
await customSchemaVectorDB.upsert({ indexName: testIndexName, vectors, metadata });
|
|
1975
|
+
|
|
1976
|
+
// Query and verify results
|
|
1977
|
+
const results = await customSchemaVectorDB.query({
|
|
1978
|
+
indexName: testIndexName,
|
|
1979
|
+
queryVector: [1, 2, 3],
|
|
1980
|
+
topK: 2,
|
|
1981
|
+
});
|
|
1982
|
+
expect(results).toHaveLength(2);
|
|
1983
|
+
expect(results[0]?.metadata?.test).toMatch(/custom_schema_/);
|
|
1984
|
+
|
|
1985
|
+
// Verify table exists in correct schema
|
|
1986
|
+
const client = await customSchemaVectorDB['pool'].connect();
|
|
1987
|
+
try {
|
|
1988
|
+
const res = await client.query(
|
|
1989
|
+
`
|
|
1990
|
+
SELECT EXISTS (
|
|
1991
|
+
SELECT FROM information_schema.tables
|
|
1992
|
+
WHERE table_schema = $1
|
|
1993
|
+
AND table_name = $2
|
|
1994
|
+
)`,
|
|
1995
|
+
[customSchema, testIndexName],
|
|
1996
|
+
);
|
|
1997
|
+
expect(res.rows[0].exists).toBe(true);
|
|
1998
|
+
} finally {
|
|
1999
|
+
client.release();
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
it('should allow same index name in different schemas', async () => {
|
|
2004
|
+
// Create same index name in both schemas
|
|
2005
|
+
await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
2006
|
+
await customSchemaVectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
2007
|
+
|
|
2008
|
+
// Insert different test data in each schema
|
|
2009
|
+
await vectorDB.upsert({
|
|
2010
|
+
indexName: testIndexName,
|
|
2011
|
+
vectors: [[1, 2, 3]],
|
|
2012
|
+
metadata: [{ test: 'default_schema' }],
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
await customSchemaVectorDB.upsert({
|
|
2016
|
+
indexName: testIndexName,
|
|
2017
|
+
vectors: [[1, 2, 3]],
|
|
2018
|
+
metadata: [{ test: 'custom_schema' }],
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
// Query both schemas and verify different results
|
|
2022
|
+
const defaultResults = await vectorDB.query({
|
|
2023
|
+
indexName: testIndexName,
|
|
2024
|
+
queryVector: [1, 2, 3],
|
|
2025
|
+
topK: 1,
|
|
2026
|
+
});
|
|
2027
|
+
const customResults = await customSchemaVectorDB.query({
|
|
2028
|
+
indexName: testIndexName,
|
|
2029
|
+
queryVector: [1, 2, 3],
|
|
2030
|
+
topK: 1,
|
|
2031
|
+
});
|
|
2032
|
+
|
|
2033
|
+
expect(defaultResults[0]?.metadata?.test).toBe('default_schema');
|
|
2034
|
+
expect(customResults[0]?.metadata?.test).toBe('custom_schema');
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
it('should maintain schema separation for all operations', async () => {
|
|
2038
|
+
// Create index in custom schema
|
|
2039
|
+
await customSchemaVectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
2040
|
+
|
|
2041
|
+
// Test index operations
|
|
2042
|
+
const stats = await customSchemaVectorDB.describeIndex(testIndexName);
|
|
2043
|
+
expect(stats.dimension).toBe(3);
|
|
2044
|
+
|
|
2045
|
+
// Test list operation
|
|
2046
|
+
const indexes = await customSchemaVectorDB.listIndexes();
|
|
2047
|
+
expect(indexes).toContain(testIndexName);
|
|
2048
|
+
|
|
2049
|
+
// Test update operation
|
|
2050
|
+
const vectors = [[7, 8, 9]];
|
|
2051
|
+
const metadata = [{ test: 'updated_in_custom_schema' }];
|
|
2052
|
+
const [id] = await customSchemaVectorDB.upsert({
|
|
2053
|
+
indexName: testIndexName,
|
|
2054
|
+
vectors,
|
|
2055
|
+
metadata,
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
// Test delete operation
|
|
2059
|
+
await customSchemaVectorDB.deleteIndexById(testIndexName, id!);
|
|
2060
|
+
|
|
2061
|
+
// Verify deletion
|
|
2062
|
+
const results = await customSchemaVectorDB.query({
|
|
2063
|
+
indexName: testIndexName,
|
|
2064
|
+
queryVector: [7, 8, 9],
|
|
2065
|
+
topK: 1,
|
|
2066
|
+
});
|
|
2067
|
+
expect(results).toHaveLength(0);
|
|
2068
|
+
});
|
|
2069
|
+
});
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2072
|
+
describe('Permission Handling', () => {
|
|
2073
|
+
const schemaRestrictedUser = 'mastra_schema_restricted';
|
|
2074
|
+
const vectorRestrictedUser = 'mastra_vector_restricted';
|
|
2075
|
+
const restrictedPassword = 'test123';
|
|
2076
|
+
const testSchema = 'test_schema';
|
|
2077
|
+
|
|
2078
|
+
const getConnectionString = (username: string) =>
|
|
2079
|
+
connectionString.replace(/(postgresql:\/\/)[^:]+:[^@]+@/, `$1${username}:${restrictedPassword}@`);
|
|
2080
|
+
|
|
2081
|
+
beforeAll(async () => {
|
|
2082
|
+
// First ensure the test schema doesn't exist from previous runs
|
|
2083
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2084
|
+
try {
|
|
2085
|
+
await adminClient.query('BEGIN');
|
|
2086
|
+
|
|
2087
|
+
// Drop the test schema if it exists from previous runs
|
|
2088
|
+
await adminClient.query(`DROP SCHEMA IF EXISTS ${testSchema} CASCADE`);
|
|
2089
|
+
|
|
2090
|
+
// Create schema restricted user with minimal permissions
|
|
2091
|
+
await adminClient.query(`
|
|
2092
|
+
DO $$
|
|
2093
|
+
BEGIN
|
|
2094
|
+
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${schemaRestrictedUser}') THEN
|
|
2095
|
+
CREATE USER ${schemaRestrictedUser} WITH PASSWORD '${restrictedPassword}' NOCREATEDB;
|
|
2096
|
+
END IF;
|
|
2097
|
+
END
|
|
2098
|
+
$$;
|
|
2099
|
+
`);
|
|
2100
|
+
|
|
2101
|
+
// Grant only connect and usage to schema restricted user
|
|
2102
|
+
await adminClient.query(`
|
|
2103
|
+
REVOKE ALL ON DATABASE ${connectionString.split('/').pop()} FROM ${schemaRestrictedUser};
|
|
2104
|
+
GRANT CONNECT ON DATABASE ${connectionString.split('/').pop()} TO ${schemaRestrictedUser};
|
|
2105
|
+
REVOKE ALL ON SCHEMA public FROM ${schemaRestrictedUser};
|
|
2106
|
+
GRANT USAGE ON SCHEMA public TO ${schemaRestrictedUser};
|
|
2107
|
+
`);
|
|
2108
|
+
|
|
2109
|
+
// Create vector restricted user with table creation permissions
|
|
2110
|
+
await adminClient.query(`
|
|
2111
|
+
DO $$
|
|
2112
|
+
BEGIN
|
|
2113
|
+
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${vectorRestrictedUser}') THEN
|
|
2114
|
+
CREATE USER ${vectorRestrictedUser} WITH PASSWORD '${restrictedPassword}' NOCREATEDB;
|
|
2115
|
+
END IF;
|
|
2116
|
+
END
|
|
2117
|
+
$$;
|
|
2118
|
+
`);
|
|
2119
|
+
|
|
2120
|
+
// Grant connect, usage, and create to vector restricted user
|
|
2121
|
+
await adminClient.query(`
|
|
2122
|
+
REVOKE ALL ON DATABASE ${connectionString.split('/').pop()} FROM ${vectorRestrictedUser};
|
|
2123
|
+
GRANT CONNECT ON DATABASE ${connectionString.split('/').pop()} TO ${vectorRestrictedUser};
|
|
2124
|
+
REVOKE ALL ON SCHEMA public FROM ${vectorRestrictedUser};
|
|
2125
|
+
GRANT USAGE, CREATE ON SCHEMA public TO ${vectorRestrictedUser};
|
|
2126
|
+
`);
|
|
2127
|
+
|
|
2128
|
+
await adminClient.query('COMMIT');
|
|
2129
|
+
} catch (e) {
|
|
2130
|
+
await adminClient.query('ROLLBACK');
|
|
2131
|
+
throw e;
|
|
2132
|
+
} finally {
|
|
2133
|
+
adminClient.release();
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
afterAll(async () => {
|
|
2138
|
+
// Clean up test users and any objects they own
|
|
2139
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2140
|
+
try {
|
|
2141
|
+
await adminClient.query('BEGIN');
|
|
2142
|
+
|
|
2143
|
+
// Helper function to drop user and their objects
|
|
2144
|
+
const dropUser = async username => {
|
|
2145
|
+
// First revoke all possible privileges and reassign objects
|
|
2146
|
+
await adminClient.query(
|
|
2147
|
+
`
|
|
2148
|
+
-- Handle object ownership (CASCADE is critical here)
|
|
2149
|
+
REASSIGN OWNED BY ${username} TO postgres;
|
|
2150
|
+
DROP OWNED BY ${username} CASCADE;
|
|
2151
|
+
|
|
2152
|
+
-- Finally drop the user
|
|
2153
|
+
DROP ROLE ${username};
|
|
2154
|
+
`,
|
|
2155
|
+
);
|
|
2156
|
+
};
|
|
2157
|
+
|
|
2158
|
+
// Drop both users
|
|
2159
|
+
await dropUser(vectorRestrictedUser);
|
|
2160
|
+
await dropUser(schemaRestrictedUser);
|
|
2161
|
+
|
|
2162
|
+
await adminClient.query('COMMIT');
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
await adminClient.query('ROLLBACK');
|
|
2165
|
+
throw e;
|
|
2166
|
+
} finally {
|
|
2167
|
+
adminClient.release();
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
describe('Schema Creation', () => {
|
|
2172
|
+
beforeEach(async () => {
|
|
2173
|
+
// Ensure schema doesn't exist before each test
|
|
2174
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2175
|
+
try {
|
|
2176
|
+
await adminClient.query('BEGIN');
|
|
2177
|
+
await adminClient.query(`DROP SCHEMA IF EXISTS ${testSchema} CASCADE`);
|
|
2178
|
+
await adminClient.query('COMMIT');
|
|
2179
|
+
} catch (e) {
|
|
2180
|
+
await adminClient.query('ROLLBACK');
|
|
2181
|
+
throw e;
|
|
2182
|
+
} finally {
|
|
2183
|
+
adminClient.release();
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
it('should fail when user lacks CREATE privilege', async () => {
|
|
2188
|
+
const restrictedDB = new PgVector({
|
|
2189
|
+
connectionString: getConnectionString(schemaRestrictedUser),
|
|
2190
|
+
schemaName: testSchema,
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
// Test schema creation directly by accessing private method
|
|
2194
|
+
await expect(async () => {
|
|
2195
|
+
const client = await restrictedDB['pool'].connect();
|
|
2196
|
+
try {
|
|
2197
|
+
await restrictedDB['setupSchema'](client);
|
|
2198
|
+
} finally {
|
|
2199
|
+
client.release();
|
|
2200
|
+
}
|
|
2201
|
+
}).rejects.toThrow(`Unable to create schema "${testSchema}". This requires CREATE privilege on the database.`);
|
|
2202
|
+
|
|
2203
|
+
// Verify schema was not created
|
|
2204
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2205
|
+
try {
|
|
2206
|
+
const res = await adminClient.query(
|
|
2207
|
+
`SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)`,
|
|
2208
|
+
[testSchema],
|
|
2209
|
+
);
|
|
2210
|
+
expect(res.rows[0].exists).toBe(false);
|
|
2211
|
+
} finally {
|
|
2212
|
+
adminClient.release();
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
await restrictedDB.disconnect();
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
it('should fail with schema creation error when creating index', async () => {
|
|
2219
|
+
const restrictedDB = new PgVector({
|
|
2220
|
+
connectionString: getConnectionString(schemaRestrictedUser),
|
|
2221
|
+
schemaName: testSchema,
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
// This should fail with the schema creation error
|
|
2225
|
+
await expect(async () => {
|
|
2226
|
+
await restrictedDB.createIndex({ indexName: 'test', dimension: 3 });
|
|
2227
|
+
}).rejects.toThrow(`Unable to create schema "${testSchema}". This requires CREATE privilege on the database.`);
|
|
2228
|
+
|
|
2229
|
+
// Verify schema was not created
|
|
2230
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2231
|
+
try {
|
|
2232
|
+
const res = await adminClient.query(
|
|
2233
|
+
`SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)`,
|
|
2234
|
+
[testSchema],
|
|
2235
|
+
);
|
|
2236
|
+
expect(res.rows[0].exists).toBe(false);
|
|
2237
|
+
} finally {
|
|
2238
|
+
adminClient.release();
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
await restrictedDB.disconnect();
|
|
2242
|
+
});
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
describe('Vector Extension', () => {
|
|
2246
|
+
beforeEach(async () => {
|
|
2247
|
+
// Create test table and grant necessary permissions
|
|
2248
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2249
|
+
try {
|
|
2250
|
+
await adminClient.query('BEGIN');
|
|
2251
|
+
|
|
2252
|
+
// First install vector extension
|
|
2253
|
+
await adminClient.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
2254
|
+
|
|
2255
|
+
// Drop existing table if any
|
|
2256
|
+
await adminClient.query('DROP TABLE IF EXISTS test CASCADE');
|
|
2257
|
+
|
|
2258
|
+
// Create test table as admin
|
|
2259
|
+
await adminClient.query('CREATE TABLE IF NOT EXISTS test (id SERIAL PRIMARY KEY, embedding vector(3))');
|
|
2260
|
+
|
|
2261
|
+
// Grant ALL permissions including index creation
|
|
2262
|
+
await adminClient.query(`
|
|
2263
|
+
GRANT ALL ON TABLE test TO ${vectorRestrictedUser};
|
|
2264
|
+
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${vectorRestrictedUser};
|
|
2265
|
+
ALTER TABLE test OWNER TO ${vectorRestrictedUser};
|
|
2266
|
+
`);
|
|
2267
|
+
|
|
2268
|
+
await adminClient.query('COMMIT');
|
|
2269
|
+
} catch (e) {
|
|
2270
|
+
await adminClient.query('ROLLBACK');
|
|
2271
|
+
throw e;
|
|
2272
|
+
} finally {
|
|
2273
|
+
adminClient.release();
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
|
|
2277
|
+
afterEach(async () => {
|
|
2278
|
+
// Clean up test table
|
|
2279
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2280
|
+
try {
|
|
2281
|
+
await adminClient.query('BEGIN');
|
|
2282
|
+
await adminClient.query('DROP TABLE IF EXISTS test CASCADE');
|
|
2283
|
+
await adminClient.query('COMMIT');
|
|
2284
|
+
} catch (e) {
|
|
2285
|
+
await adminClient.query('ROLLBACK');
|
|
2286
|
+
throw e;
|
|
2287
|
+
} finally {
|
|
2288
|
+
adminClient.release();
|
|
2289
|
+
}
|
|
2290
|
+
});
|
|
2291
|
+
|
|
2292
|
+
it('should handle lack of superuser privileges gracefully', async () => {
|
|
2293
|
+
// First ensure vector extension is not installed
|
|
2294
|
+
const adminClient = await new pg.Pool({ connectionString }).connect();
|
|
2295
|
+
try {
|
|
2296
|
+
await adminClient.query('DROP EXTENSION IF EXISTS vector CASCADE');
|
|
2297
|
+
} finally {
|
|
2298
|
+
adminClient.release();
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
const restrictedDB = new PgVector({
|
|
2302
|
+
connectionString: getConnectionString(vectorRestrictedUser),
|
|
2303
|
+
});
|
|
2304
|
+
|
|
2305
|
+
try {
|
|
2306
|
+
const warnSpy = vi.spyOn(restrictedDB['logger'], 'warn');
|
|
2307
|
+
|
|
2308
|
+
// Try to create index which will trigger vector extension installation attempt
|
|
2309
|
+
await expect(restrictedDB.createIndex({ indexName: 'test', dimension: 3 })).rejects.toThrow();
|
|
2310
|
+
|
|
2311
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
2312
|
+
expect.stringContaining('Could not install vector extension. This requires superuser privileges'),
|
|
2313
|
+
);
|
|
2314
|
+
|
|
2315
|
+
warnSpy.mockRestore();
|
|
2316
|
+
} finally {
|
|
2317
|
+
// Ensure we wait for any pending operations before disconnecting
|
|
2318
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2319
|
+
await restrictedDB.disconnect();
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
|
|
2323
|
+
it('should continue if vector extension is already installed', async () => {
|
|
2324
|
+
const restrictedDB = new PgVector({
|
|
2325
|
+
connectionString: getConnectionString(vectorRestrictedUser),
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
try {
|
|
2329
|
+
const debugSpy = vi.spyOn(restrictedDB['logger'], 'debug');
|
|
2330
|
+
|
|
2331
|
+
await restrictedDB.createIndex({ indexName: 'test', dimension: 3 });
|
|
2332
|
+
|
|
2333
|
+
expect(debugSpy).toHaveBeenCalledWith('Vector extension already installed, skipping installation');
|
|
2334
|
+
|
|
2335
|
+
debugSpy.mockRestore();
|
|
2336
|
+
} finally {
|
|
2337
|
+
// Ensure we wait for any pending operations before disconnecting
|
|
2338
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2339
|
+
await restrictedDB.disconnect();
|
|
2340
|
+
}
|
|
2341
|
+
});
|
|
2342
|
+
});
|
|
2343
|
+
});
|
|
1861
2344
|
});
|