mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-24 16:11:53 -05:00
fix(db): handle camelCase postgres metadata keys in migration schema detection (#8092)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { BetterAuthOptions } from "@better-auth/core";
|
||||
import { CamelCasePlugin, Kysely, PostgresDialect } from "kysely";
|
||||
import { Pool } from "pg";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { betterAuth } from "../auth/full";
|
||||
@@ -33,6 +34,10 @@ describe.runIf(isPostgresAvailable)(
|
||||
const customSchemaPool = new Pool({
|
||||
connectionString: `${CONNECTION_STRING}?options=-c search_path=${customSchema}`,
|
||||
});
|
||||
const customSchemaKysely = new Kysely({
|
||||
dialect: new PostgresDialect({ pool: customSchemaPool }),
|
||||
plugins: [new CamelCasePlugin()],
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Setup: Create custom schema and a table in public schema
|
||||
@@ -84,6 +89,54 @@ describe.runIf(isPostgresAvailable)(
|
||||
expect(userTableCreated?.fields).toHaveProperty("emailVerified");
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/7926
|
||||
*/
|
||||
it("should detect custom schema with CamelCasePlugin enabled", async () => {
|
||||
// Create a user table in the custom schema so it should be detected as existing
|
||||
await customSchemaPool.query(`
|
||||
CREATE TABLE IF NOT EXISTS ${customSchema}.user (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
"emailVerified" BOOLEAN NOT NULL,
|
||||
image TEXT,
|
||||
"createdAt" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
try {
|
||||
const config: BetterAuthOptions = {
|
||||
database: {
|
||||
db: customSchemaKysely,
|
||||
type: "postgres",
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
const { toBeCreated, toBeAdded } = await getMigrations(config);
|
||||
|
||||
// user table exists in custom schema, so it should NOT be in toBeCreated
|
||||
const userTableCreated = toBeCreated.find((t) => t.table === "user");
|
||||
const userTableToBeAdded = toBeAdded.find((t) => t.table === "user");
|
||||
|
||||
expect(userTableCreated).toBeUndefined();
|
||||
expect(userTableToBeAdded).toBeUndefined();
|
||||
|
||||
// Other tables should still need to be created
|
||||
const sessionTable = toBeCreated.find((t) => t.table === "session");
|
||||
expect(sessionTable).toBeDefined();
|
||||
} finally {
|
||||
// Cleanup: drop the user table so subsequent tests are not affected
|
||||
await customSchemaPool.query(
|
||||
`DROP TABLE IF EXISTS ${customSchema}.user CASCADE`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should not be affected by tables in public schema when using custom schema", async () => {
|
||||
// Even though there's a 'user' table in public schema,
|
||||
// when we use custom schema, migrations should create a new user table
|
||||
|
||||
@@ -98,14 +98,17 @@ export function matchType(
|
||||
*/
|
||||
async function getPostgresSchema(db: Kysely<unknown>): Promise<string> {
|
||||
try {
|
||||
const result = await sql<{ search_path: string }>`SHOW search_path`.execute(
|
||||
db,
|
||||
);
|
||||
if (result.rows[0]?.search_path) {
|
||||
const result = await sql<{
|
||||
search_path?: string;
|
||||
searchPath?: string;
|
||||
}>`SHOW search_path`.execute(db);
|
||||
const searchPath =
|
||||
result.rows[0]?.search_path ?? result.rows[0]?.searchPath;
|
||||
if (searchPath) {
|
||||
// search_path can be a comma-separated list like "$user, public" or '"$user", public'
|
||||
// Supabase may return escaped format like '"\$user", public'
|
||||
// We want the first non-variable schema
|
||||
const schemas = result.rows[0].search_path
|
||||
const schemas = searchPath
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
// Remove quotes and filter out variables like $user
|
||||
@@ -150,13 +153,18 @@ export async function getMigrations(config: BetterAuthOptions) {
|
||||
|
||||
// Verify the schema exists
|
||||
try {
|
||||
const schemaCheck = await sql<{ schema_name: string }>`
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
const schemaCheck = await sql<{
|
||||
schema_name?: string;
|
||||
schemaName?: string;
|
||||
}>`
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name = ${currentSchema}
|
||||
`.execute(db);
|
||||
|
||||
if (!schemaCheck.rows[0]) {
|
||||
const schemaExists =
|
||||
schemaCheck.rows[0]?.schema_name ?? schemaCheck.rows[0]?.schemaName;
|
||||
if (!schemaExists) {
|
||||
logger.warn(
|
||||
`Schema '${currentSchema}' does not exist. Tables will be inspected from available schemas. Consider creating the schema first or checking your database configuration.`,
|
||||
);
|
||||
@@ -176,16 +184,17 @@ export async function getMigrations(config: BetterAuthOptions) {
|
||||
// Get tables with their schema information
|
||||
try {
|
||||
const tablesInSchema = await sql<{
|
||||
table_name: string;
|
||||
table_name?: string;
|
||||
tableName?: string;
|
||||
}>`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = ${currentSchema}
|
||||
AND table_type = 'BASE TABLE'
|
||||
`.execute(db);
|
||||
|
||||
const tableNamesInSchema = new Set(
|
||||
tablesInSchema.rows.map((row) => row.table_name),
|
||||
tablesInSchema.rows.map((row) => row.table_name ?? row.tableName),
|
||||
);
|
||||
|
||||
// Filter to only tables that exist in the target schema
|
||||
|
||||
Reference in New Issue
Block a user