From 1c87bee37941f97ae8a8103494226770415ae316 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:21:11 -0500 Subject: [PATCH 01/11] init --- packages/sync-engine/src/cli/commands.ts | 3 + .../__tests__/migrate.openapi.test.ts | 127 +++++++ packages/sync-engine/src/database/migrate.ts | 353 ++++++++++++----- .../openapi/__tests__/fixtures/minimalSpec.ts | 103 +++++ .../openapi/__tests__/postgresAdapter.test.ts | 41 ++ .../openapi/__tests__/specFetchHelper.test.ts | 106 ++++++ .../src/openapi/__tests__/specParser.test.ts | 84 +++++ .../__tests__/writePathPlanner.test.ts | 39 ++ .../sync-engine/src/openapi/dialectAdapter.ts | 14 + packages/sync-engine/src/openapi/index.ts | 6 + .../src/openapi/postgresAdapter.ts | 104 +++++ .../src/openapi/specFetchHelper.ts | 154 ++++++++ .../sync-engine/src/openapi/specParser.ts | 357 ++++++++++++++++++ packages/sync-engine/src/openapi/types.ts | 83 ++++ .../src/openapi/writePathPlanner.ts | 43 +++ .../supabase/edge-functions/stripe-setup.ts | 6 +- 16 files changed, 1521 insertions(+), 102 deletions(-) create mode 100644 packages/sync-engine/src/database/__tests__/migrate.openapi.test.ts create mode 100644 packages/sync-engine/src/openapi/__tests__/fixtures/minimalSpec.ts create mode 100644 packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts create mode 100644 packages/sync-engine/src/openapi/__tests__/specFetchHelper.test.ts create mode 100644 packages/sync-engine/src/openapi/__tests__/specParser.test.ts create mode 100644 packages/sync-engine/src/openapi/__tests__/writePathPlanner.test.ts create mode 100644 packages/sync-engine/src/openapi/dialectAdapter.ts create mode 100644 packages/sync-engine/src/openapi/index.ts create mode 100644 packages/sync-engine/src/openapi/postgresAdapter.ts create mode 100644 packages/sync-engine/src/openapi/specFetchHelper.ts create mode 100644 packages/sync-engine/src/openapi/specParser.ts create mode 100644 packages/sync-engine/src/openapi/types.ts create mode 100644 packages/sync-engine/src/openapi/writePathPlanner.ts diff --git a/packages/sync-engine/src/cli/commands.ts b/packages/sync-engine/src/cli/commands.ts index a3a8191b..352a7c39 100644 --- a/packages/sync-engine/src/cli/commands.ts +++ b/packages/sync-engine/src/cli/commands.ts @@ -145,6 +145,7 @@ export async function backfillCommand(options: CliOptions, entityName: string): await runMigrations({ databaseUrl: config.databaseUrl, enableSigma, + stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', }) } catch (migrationError) { console.error(chalk.red('Failed to run migrations:')) @@ -278,6 +279,7 @@ export async function migrateCommand(options: CliOptions): Promise { await runMigrations({ databaseUrl, enableSigma, + stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', }) console.log(chalk.green('✓ Migrations completed successfully')) } catch (migrationError) { @@ -400,6 +402,7 @@ export async function syncCommand(options: CliOptions): Promise { await runMigrations({ databaseUrl: config.databaseUrl, enableSigma: config.enableSigma, + stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', }) } catch (migrationError) { console.error(chalk.red('Failed to run migrations:')) diff --git a/packages/sync-engine/src/database/__tests__/migrate.openapi.test.ts b/packages/sync-engine/src/database/__tests__/migrate.openapi.test.ts new file mode 100644 index 00000000..ea44adb0 --- /dev/null +++ b/packages/sync-engine/src/database/__tests__/migrate.openapi.test.ts @@ -0,0 +1,127 @@ +import fs from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import pg from 'pg' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { runMigrations } from '../migrate' +import { minimalStripeOpenApiSpec } from '../../openapi/__tests__/fixtures/minimalSpec' + +const TEST_DB_URL = process.env.TEST_POSTGRES_DB_URL +const describeWithDb = TEST_DB_URL ? describe : describe.skip + +describeWithDb('runMigrations openapi pipeline', () => { + let pool: pg.Pool + let specPath: string + let tempDir: string + + beforeAll(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openapi-migrate-test-')) + specPath = path.join(tempDir, 'spec3.json') + await fs.writeFile(specPath, JSON.stringify(minimalStripeOpenApiSpec), 'utf8') + + await runMigrations({ + databaseUrl: TEST_DB_URL!, + openApiSpecPath: specPath, + stripeApiVersion: '2020-08-27', + }) + + pool = new pg.Pool({ connectionString: TEST_DB_URL! }) + }) + + afterAll(async () => { + await pool?.end() + if (tempDir) { + await fs.rm(tempDir, { recursive: true, force: true }) + } + }) + + it('creates bootstrap internal tables and the sync_runs view', async () => { + const tablesResult = await pool.query( + `SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'stripe' + AND table_name IN ('_migrations', 'accounts', '_managed_webhooks', '_sync_runs', '_sync_obj_runs') + ORDER BY table_name` + ) + expect(tablesResult.rows.map((row) => row.table_name)).toEqual([ + '_managed_webhooks', + '_migrations', + '_sync_obj_runs', + '_sync_runs', + 'accounts', + ]) + + const viewsResult = await pool.query( + `SELECT table_name + FROM information_schema.views + WHERE table_schema = 'stripe' AND table_name = 'sync_runs'` + ) + expect(viewsResult.rows).toHaveLength(1) + }) + + it('materializes runtime-critical generated columns and naming contracts', async () => { + const subscriptionItemsColumns = await pool.query( + `SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'stripe' AND table_name = 'subscription_items'` + ) + const subscriptionColumnSet = new Set( + subscriptionItemsColumns.rows.map((row) => row.column_name as string) + ) + expect(subscriptionColumnSet.has('deleted')).toBe(true) + expect(subscriptionColumnSet.has('subscription')).toBe(true) + + const entitlementColumns = await pool.query( + `SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'stripe' AND table_name = 'active_entitlements'` + ) + expect(entitlementColumns.rows.some((row) => row.column_name === 'customer')).toBe(true) + + const customerIdColumn = await pool.query( + `SELECT is_generated + FROM information_schema.columns + WHERE table_schema = 'stripe' AND table_name = 'customers' AND column_name = 'id'` + ) + expect(customerIdColumn.rows[0]?.is_generated).toBe('ALWAYS') + + const managedWebhookColumns = await pool.query( + `SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'stripe' AND table_name = '_managed_webhooks'` + ) + const managedWebhookColumnSet = new Set( + managedWebhookColumns.rows.map((row) => row.column_name as string) + ) + expect(managedWebhookColumnSet.has('account_id')).toBe(true) + expect(managedWebhookColumnSet.has('_account_id')).toBe(false) + }) + + it('enforces one active sync run per account+triggered_by', async () => { + const accountId = 'acct_openapi_migrate_test' + await pool.query( + `INSERT INTO "stripe"."accounts" ("_raw_data", "api_key_hashes") + VALUES ($1::jsonb, ARRAY['hash_openapi'])`, + [JSON.stringify({ id: accountId, object: 'account' })] + ) + + await pool.query( + `INSERT INTO "stripe"."_sync_runs" ("_account_id", "triggered_by", "started_at") + VALUES ($1, 'worker', date_trunc('milliseconds', now()))`, + [accountId] + ) + await pool.query( + `INSERT INTO "stripe"."_sync_runs" ("_account_id", "triggered_by", "started_at") + VALUES ($1, 'sigma-worker', date_trunc('milliseconds', now()) + interval '1 second')`, + [accountId] + ) + + await expect( + pool.query( + `INSERT INTO "stripe"."_sync_runs" ("_account_id", "triggered_by", "started_at") + VALUES ($1, 'worker', date_trunc('milliseconds', now()) + interval '2 second')`, + [accountId] + ) + ).rejects.toThrow() + }) +}) diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index f7a4eeaa..181d1bf2 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -1,19 +1,20 @@ import { Client } from 'pg' -import { migrate } from 'pg-node-migrations' import { Buffer } from 'node:buffer' -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' import { createHash } from 'node:crypto' import type { ConnectionOptions } from 'node:tls' import type { Logger } from '../types' import { SIGMA_INGESTION_CONFIGS } from '../sigma/sigmaIngestionConfigs' import type { SigmaIngestionConfig } from '../sigma/sigmaIngestion' - -// Get __dirname equivalent for ESM -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - +import { + PostgresAdapter, + RUNTIME_REQUIRED_TABLES, + RUNTIME_RESOURCE_ALIASES, + SpecParser, + WritePathPlanner, + resolveOpenApiSpec, +} from '../openapi' + +const DEFAULT_STRIPE_API_VERSION = '2020-08-27' const SIGMA_BASE_COLUMNS = ['_raw_data', '_last_synced_at', '_updated_at', '_account_id'] as const // Postgres identifiers are capped at 63 bytes; long Sigma column names can collide after truncation. const PG_IDENTIFIER_MAX_BYTES = 63 @@ -25,6 +26,9 @@ type MigrationConfig = { ssl?: ConnectionOptions logger?: Logger enableSigma?: boolean + stripeApiVersion?: string + openApiSpecPath?: string + openApiCacheDir?: string } function truncateIdentifier(name: string, maxBytes: number): string { @@ -107,57 +111,7 @@ async function doesTableExist(client: Client, schema: string, tableName: string) return result.rows[0]?.exists || false } -async function renameMigrationsTableIfNeeded( - client: Client, - schema = 'stripe', - logger?: Logger -): Promise { - const oldTableExists = await doesTableExist(client, schema, 'migrations') - const newTableExists = await doesTableExist(client, schema, '_migrations') - - if (oldTableExists && !newTableExists) { - logger?.info('Renaming migrations table to _migrations') - await client.query(`ALTER TABLE "${schema}"."migrations" RENAME TO "_migrations"`) - logger?.info('Successfully renamed migrations table') - } -} - -async function cleanupSchema(client: Client, schema: string, logger?: Logger): Promise { - logger?.warn(`Migrations table is empty - dropping and recreating schema "${schema}"`) - await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`) - await client.query(`CREATE SCHEMA "${schema}"`) - logger?.info(`Schema "${schema}" has been reset`) -} - -async function connectAndMigrate( - client: Client, - migrationsDirectory: string, - config: MigrationConfig, - logOnError = false -) { - if (!fs.existsSync(migrationsDirectory)) { - config.logger?.info(`Migrations directory ${migrationsDirectory} not found, skipping`) - return - } - - const optionalConfig = { - schemaName: 'stripe', - tableName: '_migrations', - } - - try { - await migrate({ client }, migrationsDirectory, optionalConfig) - } catch (error) { - if (logOnError && error instanceof Error) { - config.logger?.error(error, 'Migration error:') - } else { - throw error - } - } -} - async function fetchTableMetadata(client: Client, schema: string, table: string) { - // Fetch columns const colsResult = await client.query( ` SELECT column_name @@ -167,7 +121,6 @@ async function fetchTableMetadata(client: Client, schema: string, table: string) [schema, table] ) - // Fetch PK columns const pkResult = await client.query( ` SELECT a.attname @@ -190,14 +143,11 @@ function shouldRecreateTable( expectedCols: string[], expectedPk: string[] ): boolean { - // Compare PKs const pkMatch = current.pk.length === expectedPk.length && expectedPk.every((p) => current.pk.includes(p)) if (!pkMatch) return true - // Compare columns const allExpected = [...new Set([...SIGMA_BASE_COLUMNS, ...expectedCols])] - if (current.columns.length !== allExpected.length) return true return allExpected.every((c) => current.columns.includes(c)) } @@ -209,7 +159,6 @@ async function ensureSigmaTableMetadata( ): Promise { const tableName = config.destinationTable - // 1. Foreign key to stripe.accounts const fkName = `fk_${tableName}_account` await client.query(` ALTER TABLE "${schema}"."${tableName}" @@ -221,7 +170,6 @@ async function ensureSigmaTableMetadata( FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); `) - // 2. Updated at trigger await client.query(` DROP TRIGGER IF EXISTS handle_updated_at ON "${schema}"."${tableName}"; `) @@ -248,31 +196,27 @@ async function createSigmaTable( '"_account_id" text NOT NULL', ] - // Explicit columns for (const col of config.upsert.extraColumns ?? []) { columnDefs.push(`"${col.column}" ${col.pgType} NOT NULL`) } - // Generated columns for (const col of generatedColumns) { - // For temporal types in generated columns, use text to avoid immutability errors + // Temporal casts in generated columns are not immutable in Postgres. const isTemporal = col.pgType === 'timestamptz' || col.pgType === 'date' || col.pgType === 'timestamp' const pgType = isTemporal ? 'text' : col.pgType const safeName = generatedNameMap.get(col.name) ?? col.name - columnDefs.push( `"${safeName}" ${pgType} GENERATED ALWAYS AS ((NULLIF(_raw_data->>'${col.name}', ''))::${pgType}) STORED` ) } - const sql = ` + await client.query(` CREATE TABLE "${schema}"."${tableName}" ( ${columnDefs.join(',\n ')}, PRIMARY KEY (${pk.map((c) => `"${c}"`).join(', ')}) ); - ` - await client.query(sql) + `) await ensureSigmaTableMetadata(client, schema, config) } @@ -282,7 +226,6 @@ async function migrateSigmaSchema( sigmaSchemaName = 'sigma' ): Promise { config.logger?.info(`Reconciling Sigma schema "${sigmaSchemaName}"`) - await client.query(`CREATE SCHEMA IF NOT EXISTS "${sigmaSchemaName}"`) for (const [key, tableConfig] of Object.entries(SIGMA_INGESTION_CONFIGS)) { @@ -293,9 +236,9 @@ async function migrateSigmaSchema( const tableName = tableConfig.destinationTable const tableExists = await doesTableExist(client, sigmaSchemaName, tableName) - const { extraColumnNames, generatedColumns, generatedNameMap } = getSigmaColumnMappings(tableConfig) + const expectedCols = [ ...extraColumnNames, ...generatedColumns.map((c) => generatedNameMap.get(c.name) ?? c.name), @@ -320,43 +263,251 @@ async function migrateSigmaSchema( } } +async function rebuildStripeSchema(client: Client, logger?: Logger): Promise { + logger?.info('Dropping and recreating stripe schema') + await client.query(`DROP SCHEMA IF EXISTS "stripe" CASCADE`) + await client.query(`CREATE SCHEMA "stripe"`) +} + +async function bootstrapInternalSchema(client: Client): Promise { + await client.query(`CREATE EXTENSION IF NOT EXISTS btree_gist`) + + await client.query(` + CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + NEW._updated_at = now(); + RETURN NEW; + END; + $$; + `) + + await client.query(` + CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + NEW.updated_at = now(); + RETURN NEW; + END; + $$; + `) + + await client.query(` + CREATE TABLE "stripe"."_migrations" ( + id serial PRIMARY KEY, + migration_name text NOT NULL UNIQUE, + applied_at timestamptz NOT NULL DEFAULT now() + ); + `) + await client.query(` + INSERT INTO "stripe"."_migrations" ("migration_name") + VALUES ('openapi_bootstrap') + ON CONFLICT ("migration_name") DO NOTHING; + `) + + await client.query(` + CREATE TABLE "stripe"."accounts" ( + "_raw_data" jsonb NOT NULL, + "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, + "api_key_hashes" text[] NOT NULL DEFAULT '{}', + "first_synced_at" timestamptz NOT NULL DEFAULT now(), + "_last_synced_at" timestamptz NOT NULL DEFAULT now(), + "_updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") + ); + `) + await client.query(` + CREATE INDEX "idx_accounts_api_key_hashes" ON "stripe"."accounts" USING GIN ("api_key_hashes"); + `) + await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";`) + await client.query(` + CREATE TRIGGER handle_updated_at + BEFORE UPDATE ON "stripe"."accounts" + FOR EACH ROW EXECUTE FUNCTION set_updated_at(); + `) + + await client.query(` + CREATE TABLE "stripe"."_managed_webhooks" ( + "id" text PRIMARY KEY, + "object" text, + "url" text NOT NULL, + "enabled_events" jsonb NOT NULL, + "description" text, + "enabled" boolean, + "livemode" boolean, + "metadata" jsonb, + "secret" text NOT NULL, + "status" text, + "api_version" text, + "created" bigint, + "last_synced_at" timestamptz, + "updated_at" timestamptz NOT NULL DEFAULT now(), + "account_id" text NOT NULL, + CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id"), + CONSTRAINT "fk_managed_webhooks_account" + FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id) + ); + `) + await client.query(` + CREATE INDEX "idx_managed_webhooks_status" ON "stripe"."_managed_webhooks" ("status"); + `) + await client.query(` + CREATE INDEX "idx_managed_webhooks_enabled" ON "stripe"."_managed_webhooks" ("enabled"); + `) + await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";`) + await client.query(` + CREATE TRIGGER handle_updated_at + BEFORE UPDATE ON "stripe"."_managed_webhooks" + FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); + `) + + await client.query(` + CREATE TABLE "stripe"."_sync_runs" ( + "_account_id" text NOT NULL, + "started_at" timestamptz NOT NULL DEFAULT now(), + "closed_at" timestamptz, + "max_concurrent" integer NOT NULL DEFAULT 3, + "triggered_by" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "started_at"), + CONSTRAINT "fk_sync_runs_account" + FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id) + ); + `) + await client.query(` + ALTER TABLE "stripe"."_sync_runs" + ADD CONSTRAINT one_active_run_per_account_triggered_by + EXCLUDE ( + "_account_id" WITH =, + COALESCE(triggered_by, 'default') WITH = + ) WHERE (closed_at IS NULL); + `) + await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";`) + await client.query(` + CREATE TRIGGER handle_updated_at + BEFORE UPDATE ON "stripe"."_sync_runs" + FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); + `) + await client.query(` + CREATE INDEX "idx_sync_runs_account_status" + ON "stripe"."_sync_runs" ("_account_id", "closed_at"); + `) + + await client.query(` + CREATE TABLE "stripe"."_sync_obj_runs" ( + "_account_id" text NOT NULL, + "run_started_at" timestamptz NOT NULL, + "object" text NOT NULL, + "status" text NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'running', 'complete', 'error')), + "started_at" timestamptz, + "completed_at" timestamptz, + "processed_count" integer NOT NULL DEFAULT 0, + "cursor" text, + "page_cursor" text, + "error_message" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "run_started_at", "object"), + CONSTRAINT "fk_sync_obj_runs_parent" + FOREIGN KEY ("_account_id", "run_started_at") + REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at") + ); + `) + await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";`) + await client.query(` + CREATE TRIGGER handle_updated_at + BEFORE UPDATE ON "stripe"."_sync_obj_runs" + FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); + `) + await client.query(` + CREATE INDEX "idx_sync_obj_runs_status" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); + `) + + await client.query(` + CREATE VIEW "stripe"."sync_runs" AS + SELECT + r._account_id as account_id, + r.started_at, + r.closed_at, + r.triggered_by, + r.max_concurrent, + COALESCE(SUM(o.processed_count), 0) as total_processed, + COUNT(o.*) as total_objects, + COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, + COUNT(*) FILTER (WHERE o.status = 'error') as error_count, + COUNT(*) FILTER (WHERE o.status = 'running') as running_count, + COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, + STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, + CASE + WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' + WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' + WHEN r.closed_at IS NULL THEN 'running' + WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' + ELSE 'complete' + END as status + FROM "stripe"."_sync_runs" r + LEFT JOIN "stripe"."_sync_obj_runs" o + ON o._account_id = r._account_id + AND o.run_started_at = r.started_at + GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; + `) +} + +async function applyOpenApiSchema(client: Client, config: MigrationConfig): Promise { + const apiVersion = config.stripeApiVersion ?? DEFAULT_STRIPE_API_VERSION + const resolvedSpec = await resolveOpenApiSpec({ + apiVersion, + openApiSpecPath: config.openApiSpecPath, + cacheDir: config.openApiCacheDir, + }) + config.logger?.info( + { + apiVersion, + source: resolvedSpec.source, + commitSha: resolvedSpec.commitSha, + cachePath: resolvedSpec.cachePath, + }, + 'Resolved Stripe OpenAPI spec' + ) + + const parser = new SpecParser() + const parsedSpec = parser.parse(resolvedSpec.spec, { + resourceAliases: RUNTIME_RESOURCE_ALIASES, + allowedTables: [...RUNTIME_REQUIRED_TABLES], + }) + const adapter = new PostgresAdapter({ schemaName: 'stripe' }) + const statements = adapter.buildAllStatements(parsedSpec.tables) + for (const statement of statements) { + await client.query(statement) + } + + const planner = new WritePathPlanner() + const writePlans = planner.buildPlans(parsedSpec.tables) + config.logger?.info( + { + tableCount: parsedSpec.tables.length, + writePlanCount: writePlans.length, + }, + 'Applied OpenAPI-generated Stripe tables' + ) +} + export async function runMigrations(config: MigrationConfig): Promise { - // Init DB const client = new Client({ connectionString: config.databaseUrl, ssl: config.ssl, connectionTimeoutMillis: 10_000, }) - const schema = 'stripe' - try { - // Run migrations await client.connect() + await rebuildStripeSchema(client, config.logger) + await bootstrapInternalSchema(client) + await applyOpenApiSchema(client, config) - // Ensure schema exists, not doing it via migration to not break current migration checksums - await client.query(`CREATE SCHEMA IF NOT EXISTS ${schema};`) - - // Rename old migrations table if it exists (one-time upgrade to internal table naming convention) - await renameMigrationsTableIfNeeded(client, schema, config.logger) - - // Check if migrations table is empty and cleanup if needed - const tableExists = await doesTableExist(client, schema, '_migrations') - if (tableExists) { - const migrationCount = await client.query( - `SELECT COUNT(*) as count FROM "${schema}"."_migrations"` - ) - const isEmpty = migrationCount.rows[0]?.count === '0' - if (isEmpty) { - await cleanupSchema(client, schema, config.logger) - } - } - - config.logger?.info('Running migrations') - - await connectAndMigrate(client, path.resolve(__dirname, './migrations'), config) - - // Run Sigma dynamic migrations after core migrations (only if sigma is enabled) if (config.enableSigma) { await migrateSigmaSchema(client, config) } diff --git a/packages/sync-engine/src/openapi/__tests__/fixtures/minimalSpec.ts b/packages/sync-engine/src/openapi/__tests__/fixtures/minimalSpec.ts new file mode 100644 index 00000000..4a22ac40 --- /dev/null +++ b/packages/sync-engine/src/openapi/__tests__/fixtures/minimalSpec.ts @@ -0,0 +1,103 @@ +import type { OpenApiSpec } from '../../types' + +export const minimalStripeOpenApiSpec: OpenApiSpec = { + openapi: '3.0.0', + info: { + version: '2020-08-27', + }, + components: { + schemas: { + customer: { + 'x-resourceId': 'customer', + oneOf: [ + { + type: 'object', + properties: { + id: { type: 'string' }, + object: { type: 'string' }, + created: { type: 'integer' }, + }, + }, + { + type: 'object', + properties: { + id: { type: 'string' }, + deleted: { type: 'boolean' }, + }, + }, + ], + }, + plan: { + 'x-resourceId': 'plan', + type: 'object', + properties: { + id: { type: 'string' }, + active: { type: 'boolean' }, + amount: { type: 'integer' }, + }, + }, + price: { + 'x-resourceId': 'price', + type: 'object', + properties: { + id: { type: 'string' }, + product: { type: 'string' }, + unit_amount: { type: 'integer' }, + metadata: { type: 'object', additionalProperties: true }, + }, + }, + product: { + 'x-resourceId': 'product', + type: 'object', + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + }, + }, + subscription_item: { + 'x-resourceId': 'subscription_item', + type: 'object', + properties: { + id: { type: 'string' }, + deleted: { type: 'boolean' }, + subscription: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + checkout_session: { + 'x-resourceId': 'checkout.session', + type: 'object', + properties: { + id: { type: 'string' }, + amount_total: { type: 'integer' }, + customer: { type: 'string', nullable: true }, + }, + }, + early_fraud_warning: { + 'x-resourceId': 'radar.early_fraud_warning', + type: 'object', + properties: { + id: { type: 'string' }, + charge: { type: 'string' }, + }, + }, + active_entitlement: { + 'x-resourceId': 'entitlements.active_entitlement', + type: 'object', + properties: { + id: { type: 'string' }, + customer: { type: 'string' }, + feature: { type: 'string' }, + }, + }, + entitlements_feature: { + 'x-resourceId': 'entitlements.feature', + type: 'object', + properties: { + id: { type: 'string' }, + lookup_key: { type: 'string' }, + }, + }, + }, + }, +} diff --git a/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts new file mode 100644 index 00000000..74a5021b --- /dev/null +++ b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest' +import { PostgresAdapter } from '../postgresAdapter' +import type { ParsedResourceTable } from '../types' + +const SAMPLE_TABLE: ParsedResourceTable = { + tableName: 'customers', + resourceId: 'customer', + sourceSchemaName: 'customer', + columns: [ + { name: 'created', type: 'bigint', nullable: false }, + { name: 'deleted', type: 'boolean', nullable: true }, + { name: 'metadata', type: 'json', nullable: true }, + { name: 'expires_at', type: 'timestamptz', nullable: true }, + ], +} + +describe('PostgresAdapter', () => { + it('emits deterministic DDL statements with runtime-required metadata columns', () => { + const adapter = new PostgresAdapter({ schemaName: 'stripe' }) + const statements = adapter.buildAllStatements([SAMPLE_TABLE]) + + expect(statements).toHaveLength(5) + expect(statements[0]).toContain('CREATE TABLE "stripe"."customers"') + expect(statements[0]).toContain('"_raw_data" jsonb NOT NULL') + expect(statements[0]).toContain('"_account_id" text NOT NULL') + expect(statements[0]).toContain('"id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED') + expect(statements[0]).toContain('"metadata" jsonb GENERATED ALWAYS AS ((_raw_data->\'metadata\')::jsonb) STORED') + // Temporal columns are stored as text generated columns for immutability safety. + expect(statements[0]).toContain('"expires_at" text GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::text) STORED') + expect(statements[1]).toContain('FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)') + expect(statements[3]).toContain('DROP TRIGGER IF EXISTS handle_updated_at') + expect(statements[4]).toContain('EXECUTE FUNCTION set_updated_at()') + }) + + it('produces stable output across repeated calls', () => { + const adapter = new PostgresAdapter({ schemaName: 'stripe' }) + const first = adapter.buildAllStatements([SAMPLE_TABLE]) + const second = adapter.buildAllStatements([SAMPLE_TABLE]) + expect(second).toEqual(first) + }) +}) diff --git a/packages/sync-engine/src/openapi/__tests__/specFetchHelper.test.ts b/packages/sync-engine/src/openapi/__tests__/specFetchHelper.test.ts new file mode 100644 index 00000000..121a804b --- /dev/null +++ b/packages/sync-engine/src/openapi/__tests__/specFetchHelper.test.ts @@ -0,0 +1,106 @@ +import fs from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { resolveOpenApiSpec } from '../specFetchHelper' +import { minimalStripeOpenApiSpec } from './fixtures/minimalSpec' + +async function createTempDir(prefix: string): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), `${prefix}-`)) +} + +describe('resolveOpenApiSpec', () => { + afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() + }) + + it('prefers explicit local spec path over cache and network', async () => { + const tempDir = await createTempDir('openapi-explicit') + const specPath = path.join(tempDir, 'spec3.json') + await fs.writeFile(specPath, JSON.stringify(minimalStripeOpenApiSpec), 'utf8') + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + + const result = await resolveOpenApiSpec({ + apiVersion: '2020-08-27', + openApiSpecPath: specPath, + cacheDir: tempDir, + }) + + expect(result.source).toBe('explicit_path') + expect(fetchMock).not.toHaveBeenCalled() + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('uses cache by api version when available', async () => { + const tempDir = await createTempDir('openapi-cache') + const cachePath = path.join(tempDir, '2020-08-27.spec3.json') + await fs.writeFile(cachePath, JSON.stringify(minimalStripeOpenApiSpec), 'utf8') + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + + const result = await resolveOpenApiSpec({ + apiVersion: '2020-08-27', + cacheDir: tempDir, + }) + + expect(result.source).toBe('cache') + expect(result.cachePath).toBe(cachePath) + expect(fetchMock).not.toHaveBeenCalled() + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('fetches from GitHub when cache misses and persists cache', async () => { + const tempDir = await createTempDir('openapi-fetch') + const fetchMock = vi.fn(async (input: URL | string) => { + const url = String(input) + if (url.includes('/commits')) { + return new Response(JSON.stringify([{ sha: 'abc123def456' }]), { status: 200 }) + } + return new Response(JSON.stringify(minimalStripeOpenApiSpec), { status: 200 }) + }) + vi.stubGlobal('fetch', fetchMock) + + const result = await resolveOpenApiSpec({ + apiVersion: '2020-08-27', + cacheDir: tempDir, + }) + + expect(result.source).toBe('github') + expect(result.commitSha).toBe('abc123def456') + + const cached = await fs.readFile(path.join(tempDir, '2020-08-27.spec3.json'), 'utf8') + expect(JSON.parse(cached)).toMatchObject({ openapi: '3.0.0' }) + expect(fetchMock).toHaveBeenCalledTimes(2) + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('throws for malformed explicit spec files', async () => { + const tempDir = await createTempDir('openapi-malformed') + const specPath = path.join(tempDir, 'spec3.json') + await fs.writeFile(specPath, JSON.stringify({ openapi: '3.0.0' }), 'utf8') + + await expect( + resolveOpenApiSpec({ + apiVersion: '2020-08-27', + openApiSpecPath: specPath, + }) + ).rejects.toThrow(/components|schemas/i) + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('fails fast when GitHub resolution fails and no explicit spec path is set', async () => { + const tempDir = await createTempDir('openapi-fail-fast') + const fetchMock = vi.fn(async () => new Response('boom', { status: 500 })) + vi.stubGlobal('fetch', fetchMock) + + await expect( + resolveOpenApiSpec({ + apiVersion: '2020-08-27', + cacheDir: tempDir, + }) + ).rejects.toThrow(/Failed to resolve Stripe OpenAPI commit/) + await fs.rm(tempDir, { recursive: true, force: true }) + }) +}) diff --git a/packages/sync-engine/src/openapi/__tests__/specParser.test.ts b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts new file mode 100644 index 00000000..83c01838 --- /dev/null +++ b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest' +import { SpecParser } from '../specParser' +import { minimalStripeOpenApiSpec } from './fixtures/minimalSpec' + +describe('SpecParser', () => { + it('parses aliased resources into deterministic tables and column types', () => { + const parser = new SpecParser() + const parsed = parser.parse(minimalStripeOpenApiSpec, { + allowedTables: ['checkout_sessions', 'customers', 'early_fraud_warnings'], + }) + + expect(parsed.tables.map((table) => table.tableName)).toEqual([ + 'checkout_sessions', + 'customers', + 'early_fraud_warnings', + ]) + + const customers = parsed.tables.find((table) => table.tableName === 'customers') + expect(customers?.columns).toEqual([ + { name: 'created', type: 'bigint', nullable: false }, + { name: 'deleted', type: 'boolean', nullable: false }, + { name: 'object', type: 'text', nullable: false }, + ]) + + const checkoutSessions = parsed.tables.find((table) => table.tableName === 'checkout_sessions') + expect(checkoutSessions?.columns).toContainEqual({ + name: 'amount_total', + type: 'bigint', + nullable: false, + }) + }) + + it('injects compatibility columns for runtime-critical tables', () => { + const parser = new SpecParser() + const parsed = parser.parse( + { + ...minimalStripeOpenApiSpec, + components: { schemas: {} }, + }, + { allowedTables: ['active_entitlements', 'subscription_items'] } + ) + + const activeEntitlements = parsed.tables.find((table) => table.tableName === 'active_entitlements') + expect(activeEntitlements?.columns).toContainEqual({ + name: 'customer', + type: 'text', + nullable: true, + }) + + const subscriptionItems = parsed.tables.find((table) => table.tableName === 'subscription_items') + expect(subscriptionItems?.columns).toContainEqual({ + name: 'deleted', + type: 'boolean', + nullable: true, + }) + expect(subscriptionItems?.columns).toContainEqual({ + name: 'subscription', + type: 'text', + nullable: true, + }) + }) + + it('is deterministic regardless of schema key order', () => { + const parser = new SpecParser() + const normal = parser.parse(minimalStripeOpenApiSpec, { + allowedTables: ['customers', 'plans', 'prices'], + }) + + const reversedSchemas = Object.fromEntries( + Object.entries(minimalStripeOpenApiSpec.components?.schemas ?? {}).reverse() + ) + const reversed = parser.parse( + { + ...minimalStripeOpenApiSpec, + components: { + schemas: reversedSchemas, + }, + }, + { allowedTables: ['customers', 'plans', 'prices'] } + ) + + expect(reversed).toEqual(normal) + }) +}) diff --git a/packages/sync-engine/src/openapi/__tests__/writePathPlanner.test.ts b/packages/sync-engine/src/openapi/__tests__/writePathPlanner.test.ts new file mode 100644 index 00000000..9d337906 --- /dev/null +++ b/packages/sync-engine/src/openapi/__tests__/writePathPlanner.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest' +import { WritePathPlanner } from '../writePathPlanner' +import type { ParsedResourceTable } from '../types' + +describe('WritePathPlanner', () => { + it('builds deterministic write plans aligned to raw json upsert assumptions', () => { + const planner = new WritePathPlanner() + const tables: ParsedResourceTable[] = [ + { + tableName: 'customers', + resourceId: 'customer', + sourceSchemaName: 'customer', + columns: [ + { name: 'deleted', type: 'boolean', nullable: true }, + { name: 'created', type: 'bigint', nullable: false }, + ], + }, + { + tableName: 'plans', + resourceId: 'plan', + sourceSchemaName: 'plan', + columns: [{ name: 'active', type: 'boolean', nullable: false }], + }, + ] + + const plans = planner.buildPlans(tables) + expect(plans.map((plan) => plan.tableName)).toEqual(['customers', 'plans']) + expect(plans[0]).toMatchObject({ + tableName: 'customers', + conflictTarget: ['id'], + extraColumns: [], + metadataColumns: ['_raw_data', '_last_synced_at', '_account_id'], + }) + expect(plans[0].generatedColumns).toEqual([ + { column: 'created', pgType: 'bigint' }, + { column: 'deleted', pgType: 'boolean' }, + ]) + }) +}) diff --git a/packages/sync-engine/src/openapi/dialectAdapter.ts b/packages/sync-engine/src/openapi/dialectAdapter.ts new file mode 100644 index 00000000..9d684d39 --- /dev/null +++ b/packages/sync-engine/src/openapi/dialectAdapter.ts @@ -0,0 +1,14 @@ +import type { ParsedResourceTable } from './types' + +export interface DialectAdapter { + /** + * Create all statements needed to materialize a single parsed table. + */ + buildTableStatements(table: ParsedResourceTable): string[] + + /** + * Create all statements needed to materialize all parsed tables. + * Implementations must be deterministic for a given input. + */ + buildAllStatements(tables: ParsedResourceTable[]): string[] +} diff --git a/packages/sync-engine/src/openapi/index.ts b/packages/sync-engine/src/openapi/index.ts new file mode 100644 index 00000000..bb17b190 --- /dev/null +++ b/packages/sync-engine/src/openapi/index.ts @@ -0,0 +1,6 @@ +export type * from './types' +export { SpecParser, RUNTIME_REQUIRED_TABLES, RUNTIME_RESOURCE_ALIASES } from './specParser' +export { PostgresAdapter } from './postgresAdapter' +export { WritePathPlanner } from './writePathPlanner' +export { resolveOpenApiSpec } from './specFetchHelper' +export type { DialectAdapter } from './dialectAdapter' diff --git a/packages/sync-engine/src/openapi/postgresAdapter.ts b/packages/sync-engine/src/openapi/postgresAdapter.ts new file mode 100644 index 00000000..87fb0b49 --- /dev/null +++ b/packages/sync-engine/src/openapi/postgresAdapter.ts @@ -0,0 +1,104 @@ +import { createHash } from 'node:crypto' +import type { DialectAdapter } from './dialectAdapter' +import type { ParsedColumn, ParsedResourceTable, ScalarType } from './types' + +const PG_IDENTIFIER_MAX_BYTES = 63 + +type PostgresAdapterOptions = { + schemaName?: string + materializeTemporalAsText?: boolean +} + +export class PostgresAdapter implements DialectAdapter { + private readonly schemaName: string + private readonly materializeTemporalAsText: boolean + + constructor(options: PostgresAdapterOptions = {}) { + this.schemaName = options.schemaName ?? 'stripe' + this.materializeTemporalAsText = options.materializeTemporalAsText ?? true + } + + buildAllStatements(tables: ParsedResourceTable[]): string[] { + return [...tables] + .sort((a, b) => a.tableName.localeCompare(b.tableName)) + .flatMap((table) => this.buildTableStatements(table)) + } + + buildTableStatements(table: ParsedResourceTable): string[] { + const quotedSchema = this.quoteIdent(this.schemaName) + const quotedTable = this.quoteIdent(table.tableName) + const generatedColumns = table.columns.map((column) => this.buildGeneratedColumn(column)) + const columnDefs = [ + '"_raw_data" jsonb NOT NULL', + '"_last_synced_at" timestamptz', + '"_updated_at" timestamptz NOT NULL DEFAULT now()', + '"_account_id" text NOT NULL', + '"id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED', + ...generatedColumns, + 'PRIMARY KEY ("id")', + ] + + const fkName = this.safeIdentifier(`fk_${table.tableName}_account`) + const accountIdxName = this.safeIdentifier(`idx_${table.tableName}_account_id`) + + return [ + `CREATE TABLE ${quotedSchema}.${quotedTable} (\n ${columnDefs.join(',\n ')}\n);`, + `ALTER TABLE ${quotedSchema}.${quotedTable} ADD CONSTRAINT ${this.quoteIdent( + fkName + )} FOREIGN KEY ("_account_id") REFERENCES ${quotedSchema}."accounts" (id);`, + `CREATE INDEX ${this.quoteIdent(accountIdxName)} ON ${quotedSchema}.${quotedTable} ("_account_id");`, + `DROP TRIGGER IF EXISTS handle_updated_at ON ${quotedSchema}.${quotedTable};`, + `CREATE TRIGGER handle_updated_at BEFORE UPDATE ON ${quotedSchema}.${quotedTable} FOR EACH ROW EXECUTE FUNCTION set_updated_at();`, + ] + } + + private buildGeneratedColumn(column: ParsedColumn): string { + const pgType = this.pgType(column.type) + const escapedPath = column.name.replace(/'/g, "''") + const expression = + pgType === 'jsonb' + ? `(_raw_data->'${escapedPath}')::jsonb` + : pgType === 'text' + ? `(_raw_data->>'${escapedPath}')::text` + : `(NULLIF(_raw_data->>'${escapedPath}', ''))::${pgType}` + + return `${this.quoteIdent(column.name)} ${pgType} GENERATED ALWAYS AS (${expression}) STORED` + } + + private pgType(type: ScalarType): string { + if (type === 'timestamptz' && this.materializeTemporalAsText) { + return 'text' + } + + switch (type) { + case 'text': + return 'text' + case 'boolean': + return 'boolean' + case 'bigint': + return 'bigint' + case 'numeric': + return 'numeric' + case 'json': + return 'jsonb' + case 'timestamptz': + return 'timestamptz' + } + } + + private quoteIdent(value: string): string { + return `"${value.replaceAll('"', '""')}"` + } + + private safeIdentifier(name: string): string { + if (Buffer.byteLength(name) <= PG_IDENTIFIER_MAX_BYTES) { + return name + } + + const hash = createHash('sha1').update(name).digest('hex').slice(0, 8) + const suffix = `_h${hash}` + const maxBaseBytes = PG_IDENTIFIER_MAX_BYTES - Buffer.byteLength(suffix) + const truncatedBase = Buffer.from(name).subarray(0, maxBaseBytes).toString('utf8') + return `${truncatedBase}${suffix}` + } +} diff --git a/packages/sync-engine/src/openapi/specFetchHelper.ts b/packages/sync-engine/src/openapi/specFetchHelper.ts new file mode 100644 index 00000000..a1ffa628 --- /dev/null +++ b/packages/sync-engine/src/openapi/specFetchHelper.ts @@ -0,0 +1,154 @@ +import os from 'node:os' +import fs from 'node:fs/promises' +import path from 'node:path' +import type { OpenApiSpec, ResolveSpecConfig, ResolvedOpenApiSpec } from './types' + +const DEFAULT_CACHE_DIR = path.join(os.tmpdir(), 'stripe-sync-openapi-cache') + +export async function resolveOpenApiSpec(config: ResolveSpecConfig): Promise { + const apiVersion = config.apiVersion + if (!apiVersion || !/^\d{4}-\d{2}-\d{2}$/.test(apiVersion)) { + throw new Error(`Invalid Stripe API version "${apiVersion}". Expected YYYY-MM-DD.`) + } + + if (config.openApiSpecPath) { + const explicitSpec = await readSpecFromPath(config.openApiSpecPath) + return { + apiVersion, + spec: explicitSpec, + source: 'explicit_path', + cachePath: config.openApiSpecPath, + } + } + + const cacheDir = config.cacheDir ?? DEFAULT_CACHE_DIR + const cachePath = getCachePath(cacheDir, apiVersion) + const cachedSpec = await tryReadCachedSpec(cachePath) + if (cachedSpec) { + return { + apiVersion, + spec: cachedSpec, + source: 'cache', + cachePath, + } + } + + const commitSha = await resolveCommitShaForApiVersion(apiVersion) + if (!commitSha) { + throw new Error( + `Could not resolve Stripe OpenAPI commit for API version ${apiVersion} and no local spec path was provided.` + ) + } + + const spec = await fetchSpecForCommit(commitSha) + validateOpenApiSpec(spec) + await tryWriteCache(cachePath, spec) + + return { + apiVersion, + spec, + source: 'github', + cachePath, + commitSha, + } +} + +async function readSpecFromPath(openApiSpecPath: string): Promise { + const raw = await fs.readFile(openApiSpecPath, 'utf8') + let parsed: unknown + try { + parsed = JSON.parse(raw) + } catch (error) { + throw new Error( + `Failed to parse OpenAPI spec at ${openApiSpecPath}: ${error instanceof Error ? error.message : String(error)}` + ) + } + validateOpenApiSpec(parsed) + return parsed +} + +async function tryReadCachedSpec(cachePath: string): Promise { + try { + const raw = await fs.readFile(cachePath, 'utf8') + const parsed = JSON.parse(raw) as unknown + validateOpenApiSpec(parsed) + return parsed + } catch { + return null + } +} + +async function tryWriteCache(cachePath: string, spec: OpenApiSpec): Promise { + try { + await fs.mkdir(path.dirname(cachePath), { recursive: true }) + await fs.writeFile(cachePath, JSON.stringify(spec), 'utf8') + } catch { + // Best effort only. Cache writes should never block migration flow. + } +} + +function getCachePath(cacheDir: string, apiVersion: string): string { + const safeVersion = apiVersion.replace(/[^0-9a-zA-Z_-]/g, '_') + return path.join(cacheDir, `${safeVersion}.spec3.json`) +} + +async function resolveCommitShaForApiVersion(apiVersion: string): Promise { + const until = `${apiVersion}T23:59:59Z` + const url = new URL('https://api.github.com/repos/stripe/openapi/commits') + url.searchParams.set('path', 'openapi/spec3.json') + url.searchParams.set('until', until) + url.searchParams.set('per_page', '1') + + const response = await fetch(url, { headers: githubHeaders() }) + if (!response.ok) { + throw new Error( + `Failed to resolve Stripe OpenAPI commit (${response.status} ${response.statusText})` + ) + } + + const json = (await response.json()) as Array<{ sha?: string }> + const commitSha = json[0]?.sha + return typeof commitSha === 'string' && commitSha.length > 0 ? commitSha : null +} + +async function fetchSpecForCommit(commitSha: string): Promise { + const url = `https://raw.githubusercontent.com/stripe/openapi/${commitSha}/openapi/spec3.json` + const response = await fetch(url, { headers: githubHeaders() }) + if (!response.ok) { + throw new Error( + `Failed to download Stripe OpenAPI spec for commit ${commitSha} (${response.status} ${response.statusText})` + ) + } + + const spec = (await response.json()) as unknown + validateOpenApiSpec(spec) + return spec +} + +function validateOpenApiSpec(spec: unknown): asserts spec is OpenApiSpec { + if (!spec || typeof spec !== 'object') { + throw new Error('OpenAPI spec is not an object') + } + const candidate = spec as Partial + if (typeof candidate.openapi !== 'string' || candidate.openapi.trim().length === 0) { + throw new Error('OpenAPI spec is missing the "openapi" field') + } + if (!candidate.components || typeof candidate.components !== 'object') { + throw new Error('OpenAPI spec is missing "components"') + } + if (!candidate.components.schemas || typeof candidate.components.schemas !== 'object') { + throw new Error('OpenAPI spec is missing "components.schemas"') + } +} + +function githubHeaders(): HeadersInit { + const headers: Record = { + Accept: 'application/vnd.github+json', + 'User-Agent': 'stripe-sync-engine-openapi', + } + const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN + if (token) { + headers.Authorization = `Bearer ${token}` + } + return headers +} diff --git a/packages/sync-engine/src/openapi/specParser.ts b/packages/sync-engine/src/openapi/specParser.ts new file mode 100644 index 00000000..bcc1b0ea --- /dev/null +++ b/packages/sync-engine/src/openapi/specParser.ts @@ -0,0 +1,357 @@ +import type { + OpenApiSchemaObject, + OpenApiSchemaOrReference, + OpenApiSpec, + ParseSpecOptions, + ParsedColumn, + ParsedOpenApiSpec, + ScalarType, +} from './types' + +const RESERVED_COLUMNS = new Set(['id', '_raw_data', '_last_synced_at', '_updated_at', '_account_id']) + +export const RUNTIME_REQUIRED_TABLES = [ + 'active_entitlements', + 'charges', + 'checkout_session_line_items', + 'checkout_sessions', + 'credit_notes', + 'customers', + 'disputes', + 'early_fraud_warnings', + 'features', + 'invoices', + 'payment_intents', + 'payment_methods', + 'plans', + 'prices', + 'products', + 'refunds', + 'reviews', + 'setup_intents', + 'subscription_items', + 'subscription_schedules', + 'subscriptions', + 'tax_ids', +] as const + +export const RUNTIME_RESOURCE_ALIASES: Record = { + active_entitlement: 'active_entitlements', + charge: 'charges', + 'checkout.session': 'checkout_sessions', + 'checkout.session.line_item': 'checkout_session_line_items', + 'checkout.session.line_items': 'checkout_session_line_items', + 'checkout.session_line_item': 'checkout_session_line_items', + credit_note: 'credit_notes', + customer: 'customers', + dispute: 'disputes', + 'entitlements.active_entitlement': 'active_entitlements', + 'entitlements.feature': 'features', + feature: 'features', + invoice: 'invoices', + item: 'checkout_session_line_items', + payment_intent: 'payment_intents', + payment_method: 'payment_methods', + plan: 'plans', + price: 'prices', + product: 'products', + 'radar.early_fraud_warning': 'early_fraud_warnings', + refund: 'refunds', + review: 'reviews', + setup_intent: 'setup_intents', + subscription: 'subscriptions', + subscription_item: 'subscription_items', + subscription_schedule: 'subscription_schedules', + tax_id: 'tax_ids', +} + +const COMPATIBILITY_COLUMNS: Record = { + active_entitlements: [{ name: 'customer', type: 'text', nullable: true }], + checkout_session_line_items: [{ name: 'checkout_session', type: 'text', nullable: true }], + customers: [{ name: 'deleted', type: 'boolean', nullable: true }], + subscription_items: [ + { name: 'deleted', type: 'boolean', nullable: true }, + { name: 'subscription', type: 'text', nullable: true }, + ], +} + +type ColumnAccumulator = { + type: ScalarType + nullable: boolean +} + +export class SpecParser { + parse(spec: OpenApiSpec, options: ParseSpecOptions = {}): ParsedOpenApiSpec { + const schemas = spec.components?.schemas + if (!schemas || typeof schemas !== 'object') { + throw new Error('OpenAPI spec is missing components.schemas') + } + + const aliases = { ...RUNTIME_RESOURCE_ALIASES, ...(options.resourceAliases ?? {}) } + const allowedTables = new Set(options.allowedTables ?? RUNTIME_REQUIRED_TABLES) + const tableMap = new Map< + string, + { + resourceId: string + sourceSchemaName: string + columns: Map + } + >() + + for (const schemaName of Object.keys(schemas).sort((a, b) => a.localeCompare(b))) { + const schema = this.resolveSchema({ $ref: `#/components/schemas/${schemaName}` }, spec) + const resourceId = schema['x-resourceId'] + if (!resourceId || typeof resourceId !== 'string') { + continue + } + + const tableName = this.resolveTableName(resourceId, aliases) + if (!allowedTables.has(tableName)) { + continue + } + + const propCandidates = this.collectPropertyCandidates( + { $ref: `#/components/schemas/${schemaName}` }, + spec + ) + const parsedColumns = this.parseColumns(propCandidates, spec) + + const existing = + tableMap.get(tableName) ?? + ({ + resourceId, + sourceSchemaName: schemaName, + columns: new Map(), + } as const) + + for (const column of parsedColumns) { + const current = existing.columns.get(column.name) + if (!current) { + existing.columns.set(column.name, { type: column.type, nullable: column.nullable }) + continue + } + existing.columns.set(column.name, { + type: this.mergeTypes(current.type, column.type), + nullable: current.nullable || column.nullable, + }) + } + + tableMap.set(tableName, existing) + } + + for (const tableName of Array.from(allowedTables).sort((a, b) => a.localeCompare(b))) { + const current = + tableMap.get(tableName) ?? + ({ + resourceId: tableName, + sourceSchemaName: 'compatibility_fallback', + columns: new Map(), + } as const) + for (const compatibilityColumn of COMPATIBILITY_COLUMNS[tableName] ?? []) { + const existing = current.columns.get(compatibilityColumn.name) + if (!existing) { + current.columns.set(compatibilityColumn.name, { + type: compatibilityColumn.type, + nullable: compatibilityColumn.nullable, + }) + } + } + tableMap.set(tableName, current) + } + + const tables = Array.from(tableMap.entries()) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([tableName, table]) => ({ + tableName, + resourceId: table.resourceId, + sourceSchemaName: table.sourceSchemaName, + columns: Array.from(table.columns.entries()) + .map(([name, value]) => ({ name, type: value.type, nullable: value.nullable })) + .sort((a, b) => a.name.localeCompare(b.name)), + })) + + return { + apiVersion: spec.info?.version ?? spec.openapi ?? 'unknown', + tables, + } + } + + private resolveTableName(resourceId: string, aliases: Record): string { + const alias = aliases[resourceId] + if (alias) { + return alias + } + + const normalized = resourceId.toLowerCase().replace(/[.]/g, '_') + return normalized.endsWith('s') ? normalized : `${normalized}s` + } + + private parseColumns( + propCandidates: Map, + spec: OpenApiSpec + ): ParsedColumn[] { + const columns: ParsedColumn[] = [] + for (const [propertyName, candidates] of Array.from(propCandidates.entries()).sort(([a], [b]) => + a.localeCompare(b) + )) { + if (RESERVED_COLUMNS.has(propertyName)) { + continue + } + const inferred = this.inferFromCandidates(candidates, spec) + columns.push({ + name: propertyName, + type: inferred.type, + nullable: inferred.nullable, + }) + } + return columns + } + + private inferFromCandidates( + candidates: OpenApiSchemaOrReference[], + spec: OpenApiSpec + ): { type: ScalarType; nullable: boolean } { + if (candidates.length === 0) { + return { type: 'text', nullable: true } + } + + let mergedType: ScalarType | null = null + let nullable = false + for (const candidate of candidates) { + const inferred = this.inferType(candidate, spec) + mergedType = mergedType ? this.mergeTypes(mergedType, inferred.type) : inferred.type + nullable = nullable || inferred.nullable + } + + return { type: mergedType ?? 'text', nullable } + } + + private mergeTypes(left: ScalarType, right: ScalarType): ScalarType { + if (left === right) return left + if (left === 'json' || right === 'json') return 'json' + if ((left === 'numeric' && right === 'bigint') || (left === 'bigint' && right === 'numeric')) { + return 'numeric' + } + if (left === 'timestamptz' && right === 'text') return 'text' + if (left === 'text' && right === 'timestamptz') return 'text' + return 'text' + } + + private inferType( + schemaOrRef: OpenApiSchemaOrReference, + spec: OpenApiSpec + ): { type: ScalarType; nullable: boolean } { + const schema = this.resolveSchema(schemaOrRef, spec) + let nullable = Boolean(schema.nullable) + + if (schema.oneOf?.length) { + const merged = this.inferFromCandidates(schema.oneOf, spec) + return { type: merged.type, nullable: nullable || merged.nullable } + } + if (schema.anyOf?.length) { + const merged = this.inferFromCandidates(schema.anyOf, spec) + return { type: merged.type, nullable: nullable || merged.nullable } + } + if (schema.allOf?.length) { + const merged = this.inferFromCandidates(schema.allOf, spec) + return { type: merged.type, nullable: nullable || merged.nullable } + } + + if (schema.type === 'boolean') return { type: 'boolean', nullable } + if (schema.type === 'integer') return { type: 'bigint', nullable } + if (schema.type === 'number') return { type: 'numeric', nullable } + if (schema.type === 'string') { + if (schema.format === 'date-time') { + return { type: 'timestamptz', nullable } + } + return { type: 'text', nullable } + } + if (schema.type === 'array') return { type: 'json', nullable } + if (schema.type === 'object') return { type: 'json', nullable } + if (schema.properties || schema.additionalProperties) return { type: 'json', nullable } + + if (schema.enum && schema.enum.length > 0) { + const values = schema.enum + if (values.every((value) => typeof value === 'boolean')) { + return { type: 'boolean', nullable } + } + if (values.every((value) => typeof value === 'number' && Number.isInteger(value))) { + return { type: 'bigint', nullable } + } + if (values.every((value) => typeof value === 'number')) { + return { type: 'numeric', nullable } + } + } + + return { type: 'text', nullable: true } + } + + private collectPropertyCandidates( + schemaOrRef: OpenApiSchemaOrReference, + spec: OpenApiSpec, + seenRefs = new Set(), + seenSchemas = new Set() + ): Map { + if (this.isReference(schemaOrRef)) { + if (seenRefs.has(schemaOrRef.$ref)) { + return new Map() + } + seenRefs.add(schemaOrRef.$ref) + } + + const schema = this.resolveSchema(schemaOrRef, spec) + if (seenSchemas.has(schema)) { + return new Map() + } + seenSchemas.add(schema) + + const merged = new Map() + const pushProp = (name: string, value: OpenApiSchemaOrReference) => { + const existing = merged.get(name) ?? [] + existing.push(value) + merged.set(name, existing) + } + + for (const [name, value] of Object.entries(schema.properties ?? {})) { + pushProp(name, value) + } + + for (const composed of [schema.allOf, schema.oneOf, schema.anyOf]) { + if (!composed) continue + for (const subSchema of composed) { + const subProps = this.collectPropertyCandidates(subSchema, spec, seenRefs, seenSchemas) + for (const [name, candidates] of subProps.entries()) { + for (const candidate of candidates) { + pushProp(name, candidate) + } + } + } + } + + return merged + } + + private resolveSchema(schemaOrRef: OpenApiSchemaOrReference, spec: OpenApiSpec): OpenApiSchemaObject { + if (!this.isReference(schemaOrRef)) { + return schemaOrRef + } + + const prefix = '#/components/schemas/' + if (!schemaOrRef.$ref.startsWith(prefix)) { + throw new Error(`Unsupported OpenAPI reference: ${schemaOrRef.$ref}`) + } + const schemaName = schemaOrRef.$ref.slice(prefix.length) + const resolved = spec.components?.schemas?.[schemaName] + if (!resolved) { + throw new Error(`Failed to resolve OpenAPI schema reference: ${schemaOrRef.$ref}`) + } + if (this.isReference(resolved)) { + return this.resolveSchema(resolved, spec) + } + return resolved + } + + private isReference(schemaOrRef: OpenApiSchemaOrReference): schemaOrRef is { $ref: string } { + return typeof (schemaOrRef as { $ref?: string }).$ref === 'string' + } +} diff --git a/packages/sync-engine/src/openapi/types.ts b/packages/sync-engine/src/openapi/types.ts new file mode 100644 index 00000000..a9d4ca4c --- /dev/null +++ b/packages/sync-engine/src/openapi/types.ts @@ -0,0 +1,83 @@ +export type OpenApiSchemaObject = { + type?: string + format?: string + nullable?: boolean + properties?: Record + items?: OpenApiSchemaOrReference + oneOf?: OpenApiSchemaOrReference[] + anyOf?: OpenApiSchemaOrReference[] + allOf?: OpenApiSchemaOrReference[] + enum?: unknown[] + additionalProperties?: boolean | OpenApiSchemaOrReference + 'x-resourceId'?: string +} + +export type OpenApiReferenceObject = { + $ref: string +} + +export type OpenApiSchemaOrReference = OpenApiSchemaObject | OpenApiReferenceObject + +export type OpenApiSpec = { + openapi: string + info?: { + version?: string + } + components?: { + schemas?: Record + } +} + +export type ScalarType = 'text' | 'boolean' | 'bigint' | 'numeric' | 'json' | 'timestamptz' + +export type ParsedColumn = { + name: string + type: ScalarType + nullable: boolean +} + +export type ParsedResourceTable = { + tableName: string + resourceId: string + sourceSchemaName: string + columns: ParsedColumn[] +} + +export type ParsedOpenApiSpec = { + apiVersion: string + tables: ParsedResourceTable[] +} + +export type ParseSpecOptions = { + /** + * Map Stripe x-resourceId values to concrete Postgres table names. + * Entries are matched case-sensitively. + */ + resourceAliases?: Record + /** + * Restrict parsing to these table names. + * If omitted, all resolvable x-resourceId entries are parsed. + */ + allowedTables?: string[] +} + +export type ResolveSpecConfig = { + apiVersion: string + openApiSpecPath?: string + cacheDir?: string +} + +export type ResolvedOpenApiSpec = { + apiVersion: string + spec: OpenApiSpec + source: 'explicit_path' | 'cache' | 'github' + cachePath?: string + commitSha?: string +} + +export type WritePlan = { + tableName: string + conflictTarget: string[] + extraColumns: Array<{ column: string; pgType: string; entryKey: string }> + metadataColumns: ['_raw_data', '_last_synced_at', '_account_id'] +} diff --git a/packages/sync-engine/src/openapi/writePathPlanner.ts b/packages/sync-engine/src/openapi/writePathPlanner.ts new file mode 100644 index 00000000..3121a5d3 --- /dev/null +++ b/packages/sync-engine/src/openapi/writePathPlanner.ts @@ -0,0 +1,43 @@ +import type { ParsedResourceTable, ScalarType, WritePlan } from './types' + +type TableWritePlan = WritePlan & { + generatedColumns: Array<{ column: string; pgType: string }> +} + +export class WritePathPlanner { + buildPlans(tables: ParsedResourceTable[]): TableWritePlan[] { + return [...tables] + .sort((a, b) => a.tableName.localeCompare(b.tableName)) + .map((table) => this.buildPlan(table)) + } + + buildPlan(table: ParsedResourceTable): TableWritePlan { + return { + tableName: table.tableName, + conflictTarget: ['id'], + extraColumns: [], + metadataColumns: ['_raw_data', '_last_synced_at', '_account_id'], + generatedColumns: table.columns + .map((column) => ({ column: column.name, pgType: this.scalarTypeToPgType(column.type) })) + .sort((a, b) => a.column.localeCompare(b.column)), + } + } + + private scalarTypeToPgType(type: ScalarType): string { + switch (type) { + case 'boolean': + return 'boolean' + case 'bigint': + return 'bigint' + case 'numeric': + return 'numeric' + case 'json': + return 'jsonb' + case 'timestamptz': + return 'timestamptz' + case 'text': + default: + return 'text' + } + } +} diff --git a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts index 6d2b775f..cf8d5f78 100644 --- a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts +++ b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts @@ -355,7 +355,11 @@ Deno.serve(async (req) => { const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '') const enableSigma = (Deno.env.get('ENABLE_SIGMA') ?? 'false') === 'true' - await runMigrations({ databaseUrl: dbUrl, enableSigma }) + await runMigrations({ + databaseUrl: dbUrl, + enableSigma, + stripeApiVersion: Deno.env.get('STRIPE_API_VERSION') ?? '2020-08-27', + }) stripeSync = new StripeSync({ poolConfig: { connectionString: dbUrl, max: 2 }, // Need 2 for advisory lock + queries From 1ca97b5b33c9cd4e654ac0225092d4277abaecd2 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:17:19 -0500 Subject: [PATCH 02/11] push --- packages/fastify-app/package.json | 2 +- .../src/test/checkoutSessions.test.ts | 6 +- packages/sync-engine/src/cli/commands.ts | 31 +----- .../postgres-sync-observability.test.ts | 4 +- .../openapi/__tests__/postgresAdapter.test.ts | 16 ++- .../src/openapi/__tests__/specParser.test.ts | 8 +- .../sync-engine/src/openapi/specParser.ts | 57 ++++++----- packages/sync-engine/src/stripeSync.ts | 42 ++++---- packages/sync-engine/src/syncObjects.ts | 97 +++++++++++++++++++ packages/sync-engine/src/types.ts | 46 ++------- 10 files changed, 180 insertions(+), 129 deletions(-) create mode 100644 packages/sync-engine/src/syncObjects.ts diff --git a/packages/fastify-app/package.json b/packages/fastify-app/package.json index 281780ee..1504884d 100644 --- a/packages/fastify-app/package.json +++ b/packages/fastify-app/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "lint": "eslint src --ext .ts", "start": "NODE_ENV=production node dist/src/server.js", - "test": "vitest" + "test": "DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@localhost:55432/postgres} TEST_POSTGRES_DB_URL=${TEST_POSTGRES_DB_URL:-postgresql://postgres:postgres@localhost:55432/postgres} STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY:-sk_test_123} STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET:-whsec_test} vitest" }, "author": "Supabase", "license": "MIT", diff --git a/packages/fastify-app/src/test/checkoutSessions.test.ts b/packages/fastify-app/src/test/checkoutSessions.test.ts index b51a3c3a..74260908 100644 --- a/packages/fastify-app/src/test/checkoutSessions.test.ts +++ b/packages/fastify-app/src/test/checkoutSessions.test.ts @@ -177,7 +177,7 @@ describe('checkout sessions', () => { `select id, object, amount_discount, amount_subtotal, amount_tax, amount_total, currency, description, price, quantity from stripe.checkout_session_line_items where checkout_session = 'cs_live_9RBjcHiy2i5p99Tf1MYM90c3SHK1grU0E6Ae6pKWR2KPA4ZiuKiB2X1Y3X'` ) - // Money columns are bigint (returned as strings by node-postgres) + // Generated numeric columns are bigint (returned as strings by node-postgres) expect(lineItems.rows).toContainEqual({ id: 'li_123', object: 'item', @@ -188,7 +188,7 @@ describe('checkout sessions', () => { currency: 'usd', description: 'T-shirt', price: 'price_1IDQm5JDPojXS6LNM31hxKzp', - quantity: 1, + quantity: '1', }) expect(lineItems.rows).toContainEqual({ @@ -201,7 +201,7 @@ describe('checkout sessions', () => { currency: 'usd', description: 'Hoodie', price: 'price_1IDQm5JDPojXS6LNM31hxKzp', - quantity: 2, + quantity: '2', }) }) }) diff --git a/packages/sync-engine/src/cli/commands.ts b/packages/sync-engine/src/cli/commands.ts index 352a7c39..9c790b91 100644 --- a/packages/sync-engine/src/cli/commands.ts +++ b/packages/sync-engine/src/cli/commands.ts @@ -12,6 +12,7 @@ import { type StripeWebSocketClient, type StripeWebhookEvent, } from '../index' +import { SYNC_OBJECTS } from '../syncObjects' import { createTunnel, type NgrokTunnel } from './ngrok' import { install, uninstall } from '../supabase' import { SIGMA_INGESTION_CONFIGS } from '../sigma/sigmaIngestionConfigs' @@ -28,28 +29,6 @@ export interface DeployOptions { export type { CliOptions } -const VALID_SYNC_OBJECTS: SyncObject[] = [ - 'all', - 'customer', - 'customer_with_entitlements', - 'invoice', - 'price', - 'product', - 'subscription', - 'subscription_schedules', - 'setup_intent', - 'payment_method', - 'dispute', - 'charge', - 'payment_intent', - 'plan', - 'tax_id', - 'credit_note', - 'early_fraud_warning', - 'refund', - 'checkout_sessions', -] - /** * Backfill command - backfills a specific entity type from Stripe. */ @@ -65,11 +44,11 @@ export async function backfillCommand(options: CliOptions, entityName: string): // Validate entity name - allow sigma table names when sigma is enabled const sigmaTableNames = enableSigma ? Object.keys(SIGMA_INGESTION_CONFIGS) : [] - const validEntities = [...VALID_SYNC_OBJECTS, ...sigmaTableNames] - if (!validEntities.includes(entityName as SyncObject)) { + const validEntities = new Set([...SYNC_OBJECTS, ...sigmaTableNames]) + if (!validEntities.has(entityName)) { const entityList = enableSigma - ? `${VALID_SYNC_OBJECTS.join(', ')}, and ${sigmaTableNames.length} sigma tables` - : VALID_SYNC_OBJECTS.join(', ') + ? `${SYNC_OBJECTS.join(', ')}, and ${sigmaTableNames.length} sigma tables` + : SYNC_OBJECTS.join(', ') console.error( chalk.red(`Error: Invalid entity name "${entityName}". Valid entities are: ${entityList}`) ) diff --git a/packages/sync-engine/src/database/postgres-sync-observability.test.ts b/packages/sync-engine/src/database/postgres-sync-observability.test.ts index 9124f4a2..2493fa39 100644 --- a/packages/sync-engine/src/database/postgres-sync-observability.test.ts +++ b/packages/sync-engine/src/database/postgres-sync-observability.test.ts @@ -10,7 +10,9 @@ describe('Observable Sync System Methods', () => { beforeAll(async () => { const databaseUrl = - process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:54322/postgres' + process.env.TEST_POSTGRES_DB_URL || + process.env.DATABASE_URL || + 'postgresql://postgres:postgres@localhost:55432/postgres' // Run migrations to ensure schema and tables exist await runMigrations({ databaseUrl }) diff --git a/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts index 74a5021b..0261237e 100644 --- a/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts +++ b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts @@ -23,11 +23,19 @@ describe('PostgresAdapter', () => { expect(statements[0]).toContain('CREATE TABLE "stripe"."customers"') expect(statements[0]).toContain('"_raw_data" jsonb NOT NULL') expect(statements[0]).toContain('"_account_id" text NOT NULL') - expect(statements[0]).toContain('"id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED') - expect(statements[0]).toContain('"metadata" jsonb GENERATED ALWAYS AS ((_raw_data->\'metadata\')::jsonb) STORED') + expect(statements[0]).toContain( + '"id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED' + ) + expect(statements[0]).toContain( + '"metadata" jsonb GENERATED ALWAYS AS ((_raw_data->\'metadata\')::jsonb) STORED' + ) // Temporal columns are stored as text generated columns for immutability safety. - expect(statements[0]).toContain('"expires_at" text GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::text) STORED') - expect(statements[1]).toContain('FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)') + expect(statements[0]).toContain( + '"expires_at" text GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::text) STORED' + ) + expect(statements[1]).toContain( + 'FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)' + ) expect(statements[3]).toContain('DROP TRIGGER IF EXISTS handle_updated_at') expect(statements[4]).toContain('EXECUTE FUNCTION set_updated_at()') }) diff --git a/packages/sync-engine/src/openapi/__tests__/specParser.test.ts b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts index 83c01838..151b69ad 100644 --- a/packages/sync-engine/src/openapi/__tests__/specParser.test.ts +++ b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts @@ -40,14 +40,18 @@ describe('SpecParser', () => { { allowedTables: ['active_entitlements', 'subscription_items'] } ) - const activeEntitlements = parsed.tables.find((table) => table.tableName === 'active_entitlements') + const activeEntitlements = parsed.tables.find( + (table) => table.tableName === 'active_entitlements' + ) expect(activeEntitlements?.columns).toContainEqual({ name: 'customer', type: 'text', nullable: true, }) - const subscriptionItems = parsed.tables.find((table) => table.tableName === 'subscription_items') + const subscriptionItems = parsed.tables.find( + (table) => table.tableName === 'subscription_items' + ) expect(subscriptionItems?.columns).toContainEqual({ name: 'deleted', type: 'boolean', diff --git a/packages/sync-engine/src/openapi/specParser.ts b/packages/sync-engine/src/openapi/specParser.ts index bcc1b0ea..23c0d6e7 100644 --- a/packages/sync-engine/src/openapi/specParser.ts +++ b/packages/sync-engine/src/openapi/specParser.ts @@ -7,33 +7,17 @@ import type { ParsedOpenApiSpec, ScalarType, } from './types' +import { RUNTIME_REQUIRED_TABLES as DEFAULT_RUNTIME_REQUIRED_TABLES } from '../syncObjects' -const RESERVED_COLUMNS = new Set(['id', '_raw_data', '_last_synced_at', '_updated_at', '_account_id']) - -export const RUNTIME_REQUIRED_TABLES = [ - 'active_entitlements', - 'charges', - 'checkout_session_line_items', - 'checkout_sessions', - 'credit_notes', - 'customers', - 'disputes', - 'early_fraud_warnings', - 'features', - 'invoices', - 'payment_intents', - 'payment_methods', - 'plans', - 'prices', - 'products', - 'refunds', - 'reviews', - 'setup_intents', - 'subscription_items', - 'subscription_schedules', - 'subscriptions', - 'tax_ids', -] as const +const RESERVED_COLUMNS = new Set([ + 'id', + '_raw_data', + '_last_synced_at', + '_updated_at', + '_account_id', +]) + +export const RUNTIME_REQUIRED_TABLES = DEFAULT_RUNTIME_REQUIRED_TABLES export const RUNTIME_RESOURCE_ALIASES: Record = { active_entitlement: 'active_entitlements', @@ -66,8 +50,18 @@ export const RUNTIME_RESOURCE_ALIASES: Record = { } const COMPATIBILITY_COLUMNS: Record = { - active_entitlements: [{ name: 'customer', type: 'text', nullable: true }], - checkout_session_line_items: [{ name: 'checkout_session', type: 'text', nullable: true }], + active_entitlements: [ + { name: 'customer', type: 'text', nullable: true }, + { name: 'object', type: 'text', nullable: true }, + { name: 'feature', type: 'text', nullable: true }, + { name: 'livemode', type: 'boolean', nullable: true }, + { name: 'lookup_key', type: 'text', nullable: true }, + ], + checkout_session_line_items: [ + { name: 'checkout_session', type: 'text', nullable: true }, + { name: 'amount_discount', type: 'bigint', nullable: true }, + { name: 'amount_tax', type: 'bigint', nullable: true }, + ], customers: [{ name: 'deleted', type: 'boolean', nullable: true }], subscription_items: [ { name: 'deleted', type: 'boolean', nullable: true }, @@ -242,7 +236,7 @@ export class SpecParser { spec: OpenApiSpec ): { type: ScalarType; nullable: boolean } { const schema = this.resolveSchema(schemaOrRef, spec) - let nullable = Boolean(schema.nullable) + const nullable = Boolean(schema.nullable) if (schema.oneOf?.length) { const merged = this.inferFromCandidates(schema.oneOf, spec) @@ -331,7 +325,10 @@ export class SpecParser { return merged } - private resolveSchema(schemaOrRef: OpenApiSchemaOrReference, spec: OpenApiSpec): OpenApiSchemaObject { + private resolveSchema( + schemaOrRef: OpenApiSchemaOrReference, + spec: OpenApiSpec + ): OpenApiSchemaObject { if (!this.isReference(schemaOrRef)) { return schemaOrRef } diff --git a/packages/sync-engine/src/stripeSync.ts b/packages/sync-engine/src/stripeSync.ts index 672fccbb..b4a365fa 100644 --- a/packages/sync-engine/src/stripeSync.ts +++ b/packages/sync-engine/src/stripeSync.ts @@ -27,6 +27,7 @@ import { sigmaCursorFromEntry, type SigmaIngestionConfig, } from './sigma/sigmaIngestion' +import { getResourceNameForSyncObject, isCoreSyncObject } from './syncObjects' /** * Identifies a specific sync run. @@ -224,14 +225,20 @@ export class StripeSync { this.upsertEarlyFraudWarning(items as Stripe.Radar.EarlyFraudWarning[], id), supportsCreatedFilter: true, }, + review: { + order: 16, // Depends on charge / payment_intent + listFn: (p) => this.stripe.reviews.list(p), + upsertFn: (items, id, bf) => this.upsertReviews(items as Stripe.Review[], id, bf), + supportsCreatedFilter: true, + }, refund: { - order: 16, // Depends on charge + order: 17, // Depends on charge listFn: (p) => this.stripe.refunds.list(p), upsertFn: (items, id, bf) => this.upsertRefunds(items as Stripe.Refund[], id, bf), supportsCreatedFilter: true, }, checkout_sessions: { - order: 17, // Depends on customer (optional) + order: 18, // Depends on customer (optional) listFn: (p) => this.stripe.checkout.sessions.list(p), upsertFn: (items, id) => this.upsertCheckoutSessions(items as Stripe.Checkout.Session[], id), @@ -1205,26 +1212,14 @@ export class StripeSync { * Get the database resource name for a SyncObject type */ private getResourceName(object: SyncObject): string { - const mapping: Record = { - customer: 'customers', - invoice: 'invoices', - price: 'prices', - product: 'products', - subscription: 'subscriptions', - subscription_schedules: 'subscription_schedules', - setup_intent: 'setup_intents', - payment_method: 'payment_methods', - dispute: 'disputes', - charge: 'charges', - payment_intent: 'payment_intents', - plan: 'plans', - tax_id: 'tax_ids', - credit_note: 'credit_notes', - early_fraud_warning: 'early_fraud_warnings', - refund: 'refunds', - checkout_sessions: 'checkout_sessions', - } - return mapping[object] || object + if (object === 'all' || object === 'customer_with_entitlements') { + return object + } + if (isCoreSyncObject(object)) { + return getResourceNameForSyncObject(object) + } + // Sigma resource names are already the destination table names. + return object } /** @@ -1638,6 +1633,9 @@ export class StripeSync { case 'early_fraud_warning': results.earlyFraudWarnings = result break + case 'review': + results.reviews = result + break case 'refund': results.refunds = result break diff --git a/packages/sync-engine/src/syncObjects.ts b/packages/sync-engine/src/syncObjects.ts new file mode 100644 index 00000000..170f82cc --- /dev/null +++ b/packages/sync-engine/src/syncObjects.ts @@ -0,0 +1,97 @@ +export const CORE_SYNC_OBJECTS = [ + 'customer', + 'invoice', + 'price', + 'product', + 'subscription', + 'subscription_schedules', + 'setup_intent', + 'payment_method', + 'dispute', + 'charge', + 'payment_intent', + 'plan', + 'tax_id', + 'credit_note', + 'early_fraud_warning', + 'review', + 'refund', + 'checkout_sessions', +] as const + +export type CoreSyncObject = (typeof CORE_SYNC_OBJECTS)[number] + +export const SYNC_OBJECTS = ['all', 'customer_with_entitlements', ...CORE_SYNC_OBJECTS] as const +export type SyncObjectName = (typeof SYNC_OBJECTS)[number] + +export const REVALIDATE_ENTITIES = [ + 'charge', + 'credit_note', + 'customer', + 'dispute', + 'invoice', + 'payment_intent', + 'payment_method', + 'plan', + 'price', + 'product', + 'refund', + 'review', + 'radar.early_fraud_warning', + 'setup_intent', + 'subscription', + 'subscription_schedule', + 'tax_id', + 'entitlements', +] as const + +export type RevalidateEntityName = (typeof REVALIDATE_ENTITIES)[number] + +type SyncObjectSchemaTableMap = { + all: [] + customer_with_entitlements: readonly ['customers', 'features', 'active_entitlements'] +} & Record + +const SYNC_OBJECT_SCHEMA_TABLES: SyncObjectSchemaTableMap = { + all: [], + customer_with_entitlements: ['customers', 'features', 'active_entitlements'], + customer: ['customers'], + invoice: ['invoices'], + price: ['prices'], + product: ['products'], + subscription: ['subscriptions', 'subscription_items'], + subscription_schedules: ['subscription_schedules'], + setup_intent: ['setup_intents'], + payment_method: ['payment_methods'], + dispute: ['disputes'], + charge: ['charges'], + payment_intent: ['payment_intents'], + plan: ['plans'], + tax_id: ['tax_ids'], + credit_note: ['credit_notes'], + early_fraud_warning: ['early_fraud_warnings'], + review: ['reviews'], + refund: ['refunds'], + checkout_sessions: ['checkout_sessions', 'checkout_session_line_items'], +} + +export const SYNC_OBJECT_TO_RESOURCE_TABLE: Record = + CORE_SYNC_OBJECTS.reduce( + (resourceMap, objectName) => { + resourceMap[objectName] = SYNC_OBJECT_SCHEMA_TABLES[objectName][0] + return resourceMap + }, + {} as Record + ) + +export const RUNTIME_REQUIRED_TABLES: ReadonlyArray = Array.from( + new Set(SYNC_OBJECTS.flatMap((objectName) => SYNC_OBJECT_SCHEMA_TABLES[objectName])) +) + +export function getResourceNameForSyncObject(object: CoreSyncObject): string { + return SYNC_OBJECT_TO_RESOURCE_TABLE[object] +} + +export function isCoreSyncObject(object: string): object is CoreSyncObject { + return Object.hasOwn(SYNC_OBJECT_TO_RESOURCE_TABLE, object) +} diff --git a/packages/sync-engine/src/types.ts b/packages/sync-engine/src/types.ts index 195cc35f..b1c7d0ce 100644 --- a/packages/sync-engine/src/types.ts +++ b/packages/sync-engine/src/types.ts @@ -1,6 +1,8 @@ import { type PoolConfig } from 'pg' -import Stripe from 'stripe' +import type Stripe from 'stripe' import type { SigmaIngestionConfig } from './sigma/sigmaIngestion' +import type { RevalidateEntityName, SyncObjectName } from './syncObjects' +export { CORE_SYNC_OBJECTS, REVALIDATE_ENTITIES, SYNC_OBJECTS } from './syncObjects' /** * Simple logger interface compatible with both pino and console @@ -11,25 +13,7 @@ export interface Logger { error(message?: unknown, ...optionalParams: unknown[]): void } -export type RevalidateEntity = - | 'charge' - | 'credit_note' - | 'customer' - | 'dispute' - | 'invoice' - | 'payment_intent' - | 'payment_method' - | 'plan' - | 'price' - | 'product' - | 'refund' - | 'review' - | 'radar.early_fraud_warning' - | 'setup_intent' - | 'subscription' - | 'subscription_schedule' - | 'tax_id' - | 'entitlements' +export type RevalidateEntity = RevalidateEntityName export type StripeSyncConfig = { /** @deprecated Use `poolConfig` with a connection string instead. */ @@ -125,26 +109,7 @@ export type StripeSyncConfig = { maxConcurrentCustomers?: number } -export type SyncObject = - | 'all' - | 'customer' - | 'customer_with_entitlements' - | 'invoice' - | 'price' - | 'product' - | 'subscription' - | 'subscription_schedules' - | 'setup_intent' - | 'payment_method' - | 'dispute' - | 'charge' - | 'payment_intent' - | 'plan' - | 'tax_id' - | 'credit_note' - | 'early_fraud_warning' - | 'refund' - | 'checkout_sessions' +export type SyncObject = SyncObjectName export interface Sync { synced: number } @@ -165,6 +130,7 @@ export interface SyncBackfill { taxIds?: Sync creditNotes?: Sync earlyFraudWarnings?: Sync + reviews?: Sync refunds?: Sync checkoutSessions?: Sync subscriptionItemChangeEventsV2Beta?: Sync From fb96b4972cac3618d263e2701d111735e3d7abfb Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 01:28:09 -0500 Subject: [PATCH 03/11] init --- packages/sync-engine/src/cli/commands.ts | 1 - packages/sync-engine/src/database/migrate.ts | 187 ++++++++++++++----- 2 files changed, 141 insertions(+), 47 deletions(-) diff --git a/packages/sync-engine/src/cli/commands.ts b/packages/sync-engine/src/cli/commands.ts index b808c6a8..54029cdd 100644 --- a/packages/sync-engine/src/cli/commands.ts +++ b/packages/sync-engine/src/cli/commands.ts @@ -8,7 +8,6 @@ import { StripeSync, runMigrations, createStripeWebSocketClient, - type SyncObject, type StripeWebSocketClient, type StripeWebhookEvent, } from '../index' diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index 528044fa..932e44fb 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -31,6 +31,15 @@ type MigrationConfig = { stripeApiVersion?: string openApiSpecPath?: string openApiCacheDir?: string + schemaName?: string +} + +function quoteIdentifier(identifier: string): string { + return `"${identifier.replaceAll('"', '""')}"` +} + +function rewriteStripeSchema(sql: string, schemaName: string): string { + return sql.replaceAll('"stripe"', quoteIdentifier(schemaName)) } function truncateIdentifier(name: string, maxBytes: number): string { @@ -178,11 +187,13 @@ function shouldRecreateTable( async function ensureSigmaTableMetadata( client: Client, schema: string, - config: SigmaIngestionConfig + config: SigmaIngestionConfig, + stripeSchemaName = 'stripe' ): Promise { const tableName = config.destinationTable const fkName = `fk_${tableName}_account` + const stripeSchemaIdent = quoteIdentifier(stripeSchemaName) await client.query(` ALTER TABLE "${schema}"."${tableName}" DROP CONSTRAINT IF EXISTS "${fkName}"; @@ -190,7 +201,7 @@ async function ensureSigmaTableMetadata( await client.query(` ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${fkName}" - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); + FOREIGN KEY ("_account_id") REFERENCES ${stripeSchemaIdent}."accounts" (id); `) await client.query(` @@ -206,7 +217,8 @@ async function ensureSigmaTableMetadata( async function createSigmaTable( client: Client, schema: string, - config: SigmaIngestionConfig + config: SigmaIngestionConfig, + stripeSchemaName = 'stripe' ): Promise { const tableName = config.destinationTable const { generatedColumns, generatedNameMap } = getSigmaColumnMappings(config) @@ -240,13 +252,14 @@ async function createSigmaTable( PRIMARY KEY (${pk.map((c) => `"${c}"`).join(', ')}) ); `) - await ensureSigmaTableMetadata(client, schema, config) + await ensureSigmaTableMetadata(client, schema, config, stripeSchemaName) } async function migrateSigmaSchema( client: Client, config: MigrationConfig, - sigmaSchemaName = 'sigma' + sigmaSchemaName = 'sigma', + stripeSchemaName = 'stripe' ): Promise { config.logger?.info(`Reconciling Sigma schema "${sigmaSchemaName}"`) await client.query(`CREATE SCHEMA IF NOT EXISTS "${sigmaSchemaName}"`) @@ -275,27 +288,33 @@ async function migrateSigmaSchema( `Schema mismatch for ${sigmaSchemaName}.${tableName} - dropping and recreating` ) await client.query(`DROP TABLE "${sigmaSchemaName}"."${tableName}" CASCADE`) - await createSigmaTable(client, sigmaSchemaName, tableConfig) + await createSigmaTable(client, sigmaSchemaName, tableConfig, stripeSchemaName) } else { - await ensureSigmaTableMetadata(client, sigmaSchemaName, tableConfig) + await ensureSigmaTableMetadata(client, sigmaSchemaName, tableConfig, stripeSchemaName) } } else { config.logger?.info(`Creating Sigma table ${sigmaSchemaName}.${tableName}`) - await createSigmaTable(client, sigmaSchemaName, tableConfig) + await createSigmaTable(client, sigmaSchemaName, tableConfig, stripeSchemaName) } } } -async function rebuildStripeSchema(client: Client, logger?: Logger): Promise { - logger?.info('Dropping and recreating stripe schema') - await client.query(`DROP SCHEMA IF EXISTS "stripe" CASCADE`) - await client.query(`CREATE SCHEMA "stripe"`) +async function rebuildStripeSchema( + client: Client, + stripeSchemaName = 'stripe', + logger?: Logger +): Promise { + const schemaIdent = quoteIdentifier(stripeSchemaName) + logger?.info(`Dropping and recreating ${stripeSchemaName} schema`) + await client.query(`DROP SCHEMA IF EXISTS ${schemaIdent} CASCADE`) + await client.query(`CREATE SCHEMA ${schemaIdent}`) } -async function bootstrapInternalSchema(client: Client): Promise { - await client.query(`CREATE EXTENSION IF NOT EXISTS btree_gist`) +async function bootstrapInternalSchema(client: Client, stripeSchemaName = 'stripe'): Promise { + const runQuery = (sql: string) => client.query(rewriteStripeSchema(sql, stripeSchemaName)) + await runQuery(`CREATE EXTENSION IF NOT EXISTS btree_gist`) - await client.query(` + await runQuery(` CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -306,7 +325,7 @@ async function bootstrapInternalSchema(client: Client): Promise { $$; `) - await client.query(` + await runQuery(` CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -317,20 +336,20 @@ async function bootstrapInternalSchema(client: Client): Promise { $$; `) - await client.query(` + await runQuery(` CREATE TABLE "stripe"."_migrations" ( id serial PRIMARY KEY, migration_name text NOT NULL UNIQUE, applied_at timestamptz NOT NULL DEFAULT now() ); `) - await client.query(` + await runQuery(` INSERT INTO "stripe"."_migrations" ("migration_name") VALUES ('openapi_bootstrap') ON CONFLICT ("migration_name") DO NOTHING; `) - await client.query(` + await runQuery(` CREATE TABLE "stripe"."accounts" ( "_raw_data" jsonb NOT NULL, "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, @@ -341,17 +360,17 @@ async function bootstrapInternalSchema(client: Client): Promise { PRIMARY KEY ("id") ); `) - await client.query(` + await runQuery(` CREATE INDEX "idx_accounts_api_key_hashes" ON "stripe"."accounts" USING GIN ("api_key_hashes"); `) - await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";`) - await client.query(` + await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";`) + await runQuery(` CREATE TRIGGER handle_updated_at BEFORE UPDATE ON "stripe"."accounts" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); `) - await client.query(` + await runQuery(` CREATE TABLE "stripe"."_managed_webhooks" ( "id" text PRIMARY KEY, "object" text, @@ -373,20 +392,20 @@ async function bootstrapInternalSchema(client: Client): Promise { FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id) ); `) - await client.query(` + await runQuery(` CREATE INDEX "idx_managed_webhooks_status" ON "stripe"."_managed_webhooks" ("status"); `) - await client.query(` + await runQuery(` CREATE INDEX "idx_managed_webhooks_enabled" ON "stripe"."_managed_webhooks" ("enabled"); `) - await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";`) - await client.query(` + await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";`) + await runQuery(` CREATE TRIGGER handle_updated_at BEFORE UPDATE ON "stripe"."_managed_webhooks" FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); `) - await client.query(` + await runQuery(` CREATE TABLE "stripe"."_sync_runs" ( "_account_id" text NOT NULL, "started_at" timestamptz NOT NULL DEFAULT now(), @@ -399,7 +418,7 @@ async function bootstrapInternalSchema(client: Client): Promise { FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id) ); `) - await client.query(` + await runQuery(` ALTER TABLE "stripe"."_sync_runs" ADD CONSTRAINT one_active_run_per_account_triggered_by EXCLUDE ( @@ -407,18 +426,18 @@ async function bootstrapInternalSchema(client: Client): Promise { COALESCE(triggered_by, 'default') WITH = ) WHERE (closed_at IS NULL); `) - await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";`) - await client.query(` + await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";`) + await runQuery(` CREATE TRIGGER handle_updated_at BEFORE UPDATE ON "stripe"."_sync_runs" FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); `) - await client.query(` + await runQuery(` CREATE INDEX "idx_sync_runs_account_status" ON "stripe"."_sync_runs" ("_account_id", "closed_at"); `) - await client.query(` + await runQuery(` CREATE TABLE "stripe"."_sync_obj_runs" ( "_account_id" text NOT NULL, "run_started_at" timestamptz NOT NULL, @@ -430,26 +449,77 @@ async function bootstrapInternalSchema(client: Client): Promise { "processed_count" integer NOT NULL DEFAULT 0, "cursor" text, "page_cursor" text, + "created_gte" integer NOT NULL DEFAULT 0, + "created_lte" integer NOT NULL DEFAULT 0, + "priority" integer NOT NULL DEFAULT 0, "error_message" text, "updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("_account_id", "run_started_at", "object"), + PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte"), CONSTRAINT "fk_sync_obj_runs_parent" FOREIGN KEY ("_account_id", "run_started_at") REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at") ); `) - await client.query(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";`) - await client.query(` + await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";`) + await runQuery(` CREATE TRIGGER handle_updated_at BEFORE UPDATE ON "stripe"."_sync_obj_runs" FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); `) - await client.query(` + await runQuery(` CREATE INDEX "idx_sync_obj_runs_status" ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); `) + await runQuery(` + CREATE INDEX "idx_sync_obj_runs_priority" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority"); + `) - await client.query(` + await runQuery(` + CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( + key TEXT PRIMARY KEY, + count INTEGER NOT NULL DEFAULT 0, + window_start TIMESTAMPTZ NOT NULL DEFAULT now() + ); + `) + await runQuery(` + CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( + rate_key TEXT, + max_requests INTEGER, + window_seconds INTEGER + ) + RETURNS VOID AS $$ + DECLARE + now TIMESTAMPTZ := clock_timestamp(); + window_length INTERVAL := make_interval(secs => window_seconds); + current_count INTEGER; + BEGIN + PERFORM pg_advisory_xact_lock(hashtext(rate_key)); + + INSERT INTO "stripe"."_rate_limits" (key, count, window_start) + VALUES (rate_key, 1, now) + ON CONFLICT (key) DO UPDATE + SET count = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN 1 + ELSE "_rate_limits".count + 1 + END, + window_start = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN now + ELSE "_rate_limits".window_start + END; + + SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; + + IF current_count > max_requests THEN + RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; + END IF; + END; + $$ LANGUAGE plpgsql; + `) + + await runQuery(` CREATE VIEW "stripe"."sync_runs" AS SELECT r._account_id as account_id, @@ -477,9 +547,33 @@ async function bootstrapInternalSchema(client: Client): Promise { AND o.run_started_at = r.started_at GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; `) + await runQuery(`DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);`) + await runQuery(` + CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS + SELECT + r."_account_id" AS account_id, + r.run_started_at, + r.object, + ROUND( + 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), + 1 + ) AS pct_complete, + COALESCE(SUM(r.processed_count), 0) AS processed + FROM "stripe"."_sync_obj_runs" r + WHERE r.run_started_at = ( + SELECT MAX(s.started_at) + FROM "stripe"."_sync_runs" s + WHERE s."_account_id" = r."_account_id" + ) + GROUP BY r."_account_id", r.run_started_at, r.object; + `) } -async function applyOpenApiSchema(client: Client, config: MigrationConfig): Promise { +async function applyOpenApiSchema( + client: Client, + config: MigrationConfig, + stripeSchemaName = 'stripe' +): Promise { const apiVersion = config.stripeApiVersion ?? DEFAULT_STRIPE_API_VERSION const resolvedSpec = await resolveOpenApiSpec({ apiVersion, @@ -501,7 +595,7 @@ async function applyOpenApiSchema(client: Client, config: MigrationConfig): Prom resourceAliases: RUNTIME_RESOURCE_ALIASES, allowedTables: [...RUNTIME_REQUIRED_TABLES], }) - const adapter = new PostgresAdapter({ schemaName: 'stripe' }) + const adapter = new PostgresAdapter({ schemaName: stripeSchemaName }) const statements = adapter.buildAllStatements(parsedSpec.tables) for (const statement of statements) { await client.query(statement) @@ -524,15 +618,16 @@ export async function runMigrations(config: MigrationConfig): Promise { ssl: config.ssl, connectionTimeoutMillis: 10_000, }) + const stripeSchemaName = config.schemaName ?? 'stripe' try { await client.connect() - await rebuildStripeSchema(client, config.logger) - await bootstrapInternalSchema(client) - await applyOpenApiSchema(client, config) + await rebuildStripeSchema(client, stripeSchemaName, config.logger) + await bootstrapInternalSchema(client, stripeSchemaName) + await applyOpenApiSchema(client, config, stripeSchemaName) if (config.enableSigma) { - await migrateSigmaSchema(client, config) + await migrateSigmaSchema(client, config, 'sigma', stripeSchemaName) } } catch (err) { config.logger?.error(err, 'Error running migrations') @@ -647,7 +742,7 @@ export async function runMigrationsFromContent( connectionTimeoutMillis: 10_000, }) - const schema = 'stripe' + const schema = config.schemaName ?? 'stripe' const tableName = '_migrations' try { @@ -656,7 +751,7 @@ export async function runMigrationsFromContent( config.logger?.info('Connected to database') // Ensure schema exists - await client.query(`CREATE SCHEMA IF NOT EXISTS ${schema};`) + await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schema)};`) // Rename old migrations table if it exists (one-time upgrade) await renameMigrationsTableIfNeeded(client, schema, config.logger) From fd9d8d8f043f6bf66626221fdb0e7e17065f9f92 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:57:42 -0500 Subject: [PATCH 04/11] update --- packages/sync-engine/package.json | 4 +- .../sync-engine/scripts/build-functions.ts | 33 - packages/sync-engine/src/cli/commands.ts | 19 +- packages/sync-engine/src/database/migrate.ts | 631 ++++++------------ .../src/database/migrations-embedded.ts | 286 -------- .../migrations/0069_internal_sync_schema.sql | 237 +++++++ packages/sync-engine/src/database/postgres.ts | 132 ++-- packages/sync-engine/src/index.ts | 4 +- .../openapi/__tests__/postgresAdapter.test.ts | 48 +- .../src/openapi/__tests__/specParser.test.ts | 42 ++ packages/sync-engine/src/openapi/index.ts | 8 +- .../src/openapi/postgresAdapter.ts | 29 +- .../src/openapi/runtimeMappings.ts | 67 ++ .../sync-engine/src/openapi/specParser.ts | 96 ++- packages/sync-engine/src/openapi/types.ts | 5 + packages/sync-engine/src/resourceRegistry.ts | 574 ++++++++-------- packages/sync-engine/src/stripeSync.ts | 23 +- packages/sync-engine/src/stripeSyncWebhook.ts | 20 +- .../edge-functions/sigma-data-worker.ts | 11 +- .../supabase/edge-functions/stripe-setup.ts | 54 +- .../supabase/edge-functions/stripe-webhook.ts | 4 + .../supabase/edge-functions/stripe-worker.ts | 4 + packages/sync-engine/src/syncObjects.ts | 97 --- packages/sync-engine/src/types.ts | 56 +- 24 files changed, 1169 insertions(+), 1315 deletions(-) delete mode 100644 packages/sync-engine/src/database/migrations-embedded.ts create mode 100644 packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql create mode 100644 packages/sync-engine/src/openapi/runtimeMappings.ts delete mode 100644 packages/sync-engine/src/syncObjects.ts diff --git a/packages/sync-engine/package.json b/packages/sync-engine/package.json index 81126915..2b208711 100644 --- a/packages/sync-engine/package.json +++ b/packages/sync-engine/package.json @@ -26,7 +26,7 @@ "scripts": { "clean": "rimraf dist", "prebuild": "npm run clean && npm run build:functions", - "build:functions": "tsx scripts/build-functions.ts && prettier --write src/database/migrations-embedded.ts", + "build:functions": "tsx scripts/build-functions.ts", "build": "tsup src/index.ts src/supabase/index.ts src/cli/index.ts src/cli/lib.ts --format esm,cjs --dts --shims && cp -r src/database/migrations dist/migrations", "lint": "eslint src --ext .ts", "test": "vitest", @@ -34,7 +34,7 @@ "test:e2e": "pnpm run build && vitest run --config vitest.e2e.config.ts && vitest run --config vitest.e2e.webhook.config.ts", "start": "pnpm run build && node dist/cli/index.js backfill", "full-sync": "pnpm run build && node dist/cli/index.js full-sync", - "generate:sigma-schema": "tsx src/sigma/schema/fetch-schema.ts" + "generate:sigma-schema": "tsx src/sigma/schema/fetch-schema.ts", }, "files": [ "dist" diff --git a/packages/sync-engine/scripts/build-functions.ts b/packages/sync-engine/scripts/build-functions.ts index b6626927..8b63a9c8 100644 --- a/packages/sync-engine/scripts/build-functions.ts +++ b/packages/sync-engine/scripts/build-functions.ts @@ -10,37 +10,6 @@ const rootDir = path.resolve(__dirname, '..') const srcDir = path.join(rootDir, 'src/supabase/edge-functions') const outDir = path.join(rootDir, 'dist/supabase/functions') -export function generateEmbeddedMigrations() { - const migrationsDir = path.join(rootDir, 'src/database/migrations') - const outFile = path.join(rootDir, 'src/database/migrations-embedded.ts') - - const files = fs - .readdirSync(migrationsDir) - .filter((f) => f.endsWith('.sql')) - .sort() - - const migrations = files.map((filename) => ({ - name: filename, - sql: fs.readFileSync(path.join(migrationsDir, filename), 'utf-8'), - })) - - const lines = [ - '// AUTO-GENERATED by scripts/build-functions.ts — do not edit manually.', - '// Run `tsx scripts/build-functions.ts` (or the build) to regenerate.', - '', - 'export type EmbeddedMigration = {', - ' name: string', - ' sql: string', - '}', - '', - `export const embeddedMigrations: EmbeddedMigration[] = ${JSON.stringify(migrations, null, 2)}`, - '', - ] - - fs.writeFileSync(outFile, lines.join('\n'), 'utf-8') - console.log(`Generated ${outFile} with ${migrations.length} migrations`) -} - if (!fs.existsSync(srcDir)) { console.error(`Source directory not found: ${srcDir}`) process.exit(1) @@ -55,8 +24,6 @@ const files = fs.readdirSync(srcDir).filter((f) => f.endsWith('.ts')) console.log(`Found ${files.length} functions in ${srcDir}`) async function build() { - generateEmbeddedMigrations() - for (const file of files) { const name = path.basename(file, '.ts') const entryPoint = path.join(srcDir, file) diff --git a/packages/sync-engine/src/cli/commands.ts b/packages/sync-engine/src/cli/commands.ts index 54029cdd..4de7780a 100644 --- a/packages/sync-engine/src/cli/commands.ts +++ b/packages/sync-engine/src/cli/commands.ts @@ -11,9 +11,8 @@ import { type StripeWebSocketClient, type StripeWebhookEvent, } from '../index' -import { SYNC_OBJECTS } from '../syncObjects' import { createTunnel, type NgrokTunnel } from './ngrok' -import { type StripeObject } from '../resourceRegistry' +import { SYNC_OBJECTS, type StripeObject } from '../resourceRegistry' import { install, uninstall } from '../supabase' import { SIGMA_INGESTION_CONFIGS } from '../sigma/sigmaIngestionConfigs' @@ -204,10 +203,14 @@ export async function backfillCommand(options: CliOptions, entityName: string): // Run migrations first (will check for legacy installations and throw if detected) try { + const schemaName = process.env.SYNC_SCHEMA_NAME ?? undefined + const syncTablesSchemaName = process.env.SYNC_TABLES_SCHEMA_NAME ?? undefined await runMigrations({ databaseUrl: config.databaseUrl, enableSigma, stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', + schemaName, + syncTablesSchemaName, }) } catch (migrationError) { console.error(chalk.red('Failed to run migrations:')) @@ -337,10 +340,14 @@ export async function migrateCommand(options: CliOptions): Promise { } try { + const schemaName = process.env.SYNC_SCHEMA_NAME ?? undefined + const syncTablesSchemaName = process.env.SYNC_TABLES_SCHEMA_NAME ?? undefined await runMigrations({ databaseUrl, enableSigma, stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', + schemaName, + syncTablesSchemaName, }) console.log(chalk.green('✓ Migrations completed successfully')) } catch (migrationError) { @@ -460,10 +467,14 @@ export async function syncCommand(options: CliOptions): Promise { // 1. Run migrations (will check for legacy installations and throw if detected) try { + const schemaName = process.env.SYNC_SCHEMA_NAME ?? undefined + const syncTablesSchemaName = process.env.SYNC_TABLES_SCHEMA_NAME ?? undefined await runMigrations({ databaseUrl: config.databaseUrl, enableSigma: config.enableSigma, stripeApiVersion: process.env.STRIPE_API_VERSION || '2020-08-27', + schemaName, + syncTablesSchemaName, }) } catch (migrationError) { console.error(chalk.red('Failed to run migrations:')) @@ -717,9 +728,13 @@ export async function fullSyncCommand( // Run migrations first try { + const schemaName = process.env.SYNC_SCHEMA_NAME ?? undefined + const syncTablesSchemaName = process.env.SYNC_TABLES_SCHEMA_NAME ?? undefined await runMigrations({ databaseUrl: config.databaseUrl, enableSigma, + schemaName, + syncTablesSchemaName, }) } catch (migrationError) { console.error(chalk.red('Failed to run migrations:')) diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index 932e44fb..565c1f5c 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -1,20 +1,22 @@ import { Client } from 'pg' +import { migrate } from 'pg-node-migrations' import { Buffer } from 'node:buffer' -import crypto from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' import { createHash } from 'node:crypto' +import { fileURLToPath } from 'node:url' import type { ConnectionOptions } from 'node:tls' import type { Logger } from '../types' import { SIGMA_INGESTION_CONFIGS } from '../sigma/sigmaIngestionConfigs' import type { SigmaIngestionConfig } from '../sigma/sigmaIngestion' import { + OPENAPI_RESOURCE_TABLE_ALIASES, PostgresAdapter, RUNTIME_REQUIRED_TABLES, - RUNTIME_RESOURCE_ALIASES, SpecParser, WritePathPlanner, resolveOpenApiSpec, } from '../openapi' -import type { EmbeddedMigration } from './migrations-embedded' const DEFAULT_STRIPE_API_VERSION = '2020-08-27' const SIGMA_BASE_COLUMNS = ['_raw_data', '_last_synced_at', '_updated_at', '_account_id'] as const @@ -22,6 +24,8 @@ const SIGMA_BASE_COLUMNS = ['_raw_data', '_last_synced_at', '_updated_at', '_acc const PG_IDENTIFIER_MAX_BYTES = 63 const SIGMA_COLUMN_HASH_PREFIX = '_h' const SIGMA_COLUMN_HASH_BYTES = 8 +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) type MigrationConfig = { databaseUrl: string @@ -32,16 +36,14 @@ type MigrationConfig = { openApiSpecPath?: string openApiCacheDir?: string schemaName?: string + /** Schema for sync metadata tables (accounts, _sync_runs, etc.). Defaults to schemaName. */ + syncTablesSchemaName?: string } function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('"', '""')}"` } -function rewriteStripeSchema(sql: string, schemaName: string): string { - return sql.replaceAll('"stripe"', quoteIdentifier(schemaName)) -} - function truncateIdentifier(name: string, maxBytes: number): string { if (Buffer.byteLength(name) <= maxBytes) return name return Buffer.from(name).subarray(0, maxBytes).toString('utf8') @@ -143,6 +145,32 @@ async function cleanupSchema(client: Client, schema: string, logger?: Logger): P await client.query(`CREATE SCHEMA "${schema}"`) logger?.info(`Schema "${schema}" has been reset`) } + +async function connectAndMigrate( + client: Client, + migrationsDirectory: string, + schemaName: string, + config: MigrationConfig, + logOnError = true +): Promise { + if (!fs.existsSync(migrationsDirectory)) { + throw new Error(`Migrations directory not found. ${migrationsDirectory} does not exist.`) + } + + const optionalConfig = { + schemaName, + tableName: '_migrations', + } + + try { + await migrate({ client }, migrationsDirectory, optionalConfig) + } catch (error) { + if (logOnError && error instanceof Error) { + config.logger?.error(error, 'Migration error:') + } + throw error + } +} async function fetchTableMetadata(client: Client, schema: string, table: string) { const colsResult = await client.query( ` @@ -299,280 +327,109 @@ async function migrateSigmaSchema( } } -async function rebuildStripeSchema( - client: Client, - stripeSchemaName = 'stripe', - logger?: Logger -): Promise { - const schemaIdent = quoteIdentifier(stripeSchemaName) - logger?.info(`Dropping and recreating ${stripeSchemaName} schema`) - await client.query(`DROP SCHEMA IF EXISTS ${schemaIdent} CASCADE`) - await client.query(`CREATE SCHEMA ${schemaIdent}`) +/** Run SQL, ignoring "already exists" errors (additive apply). Rethrows other errors. */ +async function runSqlAdditive(client: Client, sql: string, logger?: Logger): Promise { + try { + await client.query(sql) + } catch (err: unknown) { + const code = (err as { code?: string })?.code + // 42P07=duplicate_table, 42710=duplicate_object (index/constraint), 42P16=invalid_table_definition + if (code === '42P07' || code === '42710' || code === '42P16' || code === '42701') { + logger?.info?.({ code }, 'Skipping already-existing object (additive apply)') + return + } + throw err + } } -async function bootstrapInternalSchema(client: Client, stripeSchemaName = 'stripe'): Promise { - const runQuery = (sql: string) => client.query(rewriteStripeSchema(sql, stripeSchemaName)) - await runQuery(`CREATE EXTENSION IF NOT EXISTS btree_gist`) - - await runQuery(` - CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger - LANGUAGE plpgsql - AS $$ - BEGIN - NEW._updated_at = now(); - RETURN NEW; - END; - $$; - `) - - await runQuery(` - CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger - LANGUAGE plpgsql - AS $$ - BEGIN - NEW.updated_at = now(); - RETURN NEW; - END; - $$; - `) +type MigrationMarkerColumn = 'migration_name' | 'name' - await runQuery(` - CREATE TABLE "stripe"."_migrations" ( - id serial PRIMARY KEY, - migration_name text NOT NULL UNIQUE, - applied_at timestamptz NOT NULL DEFAULT now() - ); - `) - await runQuery(` - INSERT INTO "stripe"."_migrations" ("migration_name") - VALUES ('openapi_bootstrap') - ON CONFLICT ("migration_name") DO NOTHING; - `) +async function getMigrationMarkerColumn( + client: Client, + schema: string +): Promise { + const colCheck = await client.query( + `SELECT column_name FROM information_schema.columns + WHERE table_schema = $1 AND table_name = '_migrations' AND column_name IN ('migration_name', 'name')`, + [schema] + ) + const hasMigrationName = colCheck.rows.some((r) => r.column_name === 'migration_name') + if (hasMigrationName) return 'migration_name' + const hasName = colCheck.rows.some((r) => r.column_name === 'name') + if (hasName) return 'name' + throw new Error( + `Unsupported _migrations schema in "${schema}" (expected migration_name or name column).` + ) +} - await runQuery(` - CREATE TABLE "stripe"."accounts" ( - "_raw_data" jsonb NOT NULL, - "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, - "api_key_hashes" text[] NOT NULL DEFAULT '{}', - "first_synced_at" timestamptz NOT NULL DEFAULT now(), - "_last_synced_at" timestamptz NOT NULL DEFAULT now(), - "_updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("id") - ); - `) - await runQuery(` - CREATE INDEX "idx_accounts_api_key_hashes" ON "stripe"."accounts" USING GIN ("api_key_hashes"); - `) - await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";`) - await runQuery(` - CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."accounts" - FOR EACH ROW EXECUTE FUNCTION set_updated_at(); - `) +async function insertMigrationMarker( + client: Client, + schema: string, + markerColumn: MigrationMarkerColumn, + marker: string, + hash: string +): Promise { + if (markerColumn === 'migration_name') { + await client.query( + `INSERT INTO "${schema}"."_migrations" ("migration_name") VALUES ($1) ON CONFLICT ("migration_name") DO NOTHING`, + [marker] + ) + return + } - await runQuery(` - CREATE TABLE "stripe"."_managed_webhooks" ( - "id" text PRIMARY KEY, - "object" text, - "url" text NOT NULL, - "enabled_events" jsonb NOT NULL, - "description" text, - "enabled" boolean, - "livemode" boolean, - "metadata" jsonb, - "secret" text NOT NULL, - "status" text, - "api_version" text, - "created" bigint, - "last_synced_at" timestamptz, - "updated_at" timestamptz NOT NULL DEFAULT now(), - "account_id" text NOT NULL, - CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id"), - CONSTRAINT "fk_managed_webhooks_account" - FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id) - ); - `) - await runQuery(` - CREATE INDEX "idx_managed_webhooks_status" ON "stripe"."_managed_webhooks" ("status"); - `) - await runQuery(` - CREATE INDEX "idx_managed_webhooks_enabled" ON "stripe"."_managed_webhooks" ("enabled"); - `) - await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";`) - await runQuery(` - CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_managed_webhooks" - FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); - `) + const idResult = await client.query( + `SELECT COALESCE(MIN(id), 0) - 1 as next_id FROM "${schema}"."_migrations"` + ) + const nextId = Number(idResult.rows[0]?.next_id ?? -1) + await client.query( + `INSERT INTO "${schema}"."_migrations" (id, name, hash) VALUES ($1, $2, $3) ON CONFLICT (name) DO NOTHING`, + [nextId, marker, hash] + ) +} - await runQuery(` - CREATE TABLE "stripe"."_sync_runs" ( - "_account_id" text NOT NULL, - "started_at" timestamptz NOT NULL DEFAULT now(), - "closed_at" timestamptz, - "max_concurrent" integer NOT NULL DEFAULT 3, - "triggered_by" text, - "updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("_account_id", "started_at"), - CONSTRAINT "fk_sync_runs_account" - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id) - ); - `) - await runQuery(` - ALTER TABLE "stripe"."_sync_runs" - ADD CONSTRAINT one_active_run_per_account_triggered_by - EXCLUDE ( - "_account_id" WITH =, - COALESCE(triggered_by, 'default') WITH = - ) WHERE (closed_at IS NULL); - `) - await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";`) - await runQuery(` - CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_runs" - FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); - `) - await runQuery(` - CREATE INDEX "idx_sync_runs_account_status" - ON "stripe"."_sync_runs" ("_account_id", "closed_at"); - `) +function computeOpenApiFingerprint(spec: unknown): string { + // Use a content-derived fingerprint so marker identity is stable across spec sources. + return createHash('sha256').update(JSON.stringify(spec)).digest('hex').slice(0, 16) +} - await runQuery(` - CREATE TABLE "stripe"."_sync_obj_runs" ( - "_account_id" text NOT NULL, - "run_started_at" timestamptz NOT NULL, - "object" text NOT NULL, - "status" text NOT NULL DEFAULT 'pending' - CHECK (status IN ('pending', 'running', 'complete', 'error')), - "started_at" timestamptz, - "completed_at" timestamptz, - "processed_count" integer NOT NULL DEFAULT 0, - "cursor" text, - "page_cursor" text, - "created_gte" integer NOT NULL DEFAULT 0, - "created_lte" integer NOT NULL DEFAULT 0, - "priority" integer NOT NULL DEFAULT 0, - "error_message" text, - "updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte"), - CONSTRAINT "fk_sync_obj_runs_parent" - FOREIGN KEY ("_account_id", "run_started_at") - REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at") - ); - `) - await runQuery(`DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";`) - await runQuery(` - CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_obj_runs" - FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); - `) - await runQuery(` - CREATE INDEX "idx_sync_obj_runs_status" - ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); - `) - await runQuery(` - CREATE INDEX "idx_sync_obj_runs_priority" - ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority"); - `) +function isLegacyOpenApiCommitMarker( + marker: string, + dataSchema: string, + apiVersion: string +): boolean { + const markerPrefix = `openapi:${dataSchema}:${apiVersion}:` + if (!marker.startsWith(markerPrefix)) { + return false + } + const suffix = marker.slice(markerPrefix.length) + return /^[0-9a-f]{40}$/i.test(suffix) +} - await runQuery(` - CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( - key TEXT PRIMARY KEY, - count INTEGER NOT NULL DEFAULT 0, - window_start TIMESTAMPTZ NOT NULL DEFAULT now() - ); - `) - await runQuery(` - CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( - rate_key TEXT, - max_requests INTEGER, - window_seconds INTEGER - ) - RETURNS VOID AS $$ - DECLARE - now TIMESTAMPTZ := clock_timestamp(); - window_length INTERVAL := make_interval(secs => window_seconds); - current_count INTEGER; - BEGIN - PERFORM pg_advisory_xact_lock(hashtext(rate_key)); - - INSERT INTO "stripe"."_rate_limits" (key, count, window_start) - VALUES (rate_key, 1, now) - ON CONFLICT (key) DO UPDATE - SET count = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN 1 - ELSE "_rate_limits".count + 1 - END, - window_start = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN now - ELSE "_rate_limits".window_start - END; - - SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; - - IF current_count > max_requests THEN - RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; - END IF; - END; - $$ LANGUAGE plpgsql; - `) +async function listOpenApiMarkersForVersion( + client: Client, + schema: string, + markerColumn: MigrationMarkerColumn, + dataSchema: string, + apiVersion: string +): Promise { + const markerPrefix = `openapi:${dataSchema}:${apiVersion}:` + const result = await client.query<{ marker: string }>( + `SELECT "${markerColumn}" AS marker + FROM "${schema}"."_migrations" + WHERE "${markerColumn}" LIKE $1`, + [`${markerPrefix}%`] + ) - await runQuery(` - CREATE VIEW "stripe"."sync_runs" AS - SELECT - r._account_id as account_id, - r.started_at, - r.closed_at, - r.triggered_by, - r.max_concurrent, - COALESCE(SUM(o.processed_count), 0) as total_processed, - COUNT(o.*) as total_objects, - COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, - COUNT(*) FILTER (WHERE o.status = 'error') as error_count, - COUNT(*) FILTER (WHERE o.status = 'running') as running_count, - COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, - STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, - CASE - WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' - WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' - WHEN r.closed_at IS NULL THEN 'running' - WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' - ELSE 'complete' - END as status - FROM "stripe"."_sync_runs" r - LEFT JOIN "stripe"."_sync_obj_runs" o - ON o._account_id = r._account_id - AND o.run_started_at = r.started_at - GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; - `) - await runQuery(`DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);`) - await runQuery(` - CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS - SELECT - r."_account_id" AS account_id, - r.run_started_at, - r.object, - ROUND( - 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), - 1 - ) AS pct_complete, - COALESCE(SUM(r.processed_count), 0) AS processed - FROM "stripe"."_sync_obj_runs" r - WHERE r.run_started_at = ( - SELECT MAX(s.started_at) - FROM "stripe"."_sync_runs" s - WHERE s."_account_id" = r."_account_id" - ) - GROUP BY r."_account_id", r.run_started_at, r.object; - `) + return result.rows + .map((row) => row.marker) + .filter((marker): marker is string => typeof marker === 'string') } async function applyOpenApiSchema( client: Client, config: MigrationConfig, - stripeSchemaName = 'stripe' + dataSchema: string, + syncSchema: string ): Promise { const apiVersion = config.stripeApiVersion ?? DEFAULT_STRIPE_API_VERSION const resolvedSpec = await resolveOpenApiSpec({ @@ -580,33 +437,80 @@ async function applyOpenApiSchema( openApiSpecPath: config.openApiSpecPath, cacheDir: config.openApiCacheDir, }) + const fingerprint = computeOpenApiFingerprint(resolvedSpec.spec) + const marker = `openapi:${dataSchema}:${apiVersion}:${fingerprint}` + config.logger?.info( { apiVersion, source: resolvedSpec.source, commitSha: resolvedSpec.commitSha, - cachePath: resolvedSpec.cachePath, + fingerprint, }, 'Resolved Stripe OpenAPI spec' ) + // Ensure _migrations exists (bootstrap creates it; may use pg-node-migrations format) + const migrationsExists = await doesTableExist(client, syncSchema, '_migrations') + if (!migrationsExists) { + throw new Error(`_migrations table not found in schema "${syncSchema}". Run bootstrap first.`) + } + + const markerColumn = await getMigrationMarkerColumn(client, syncSchema) + const existingMarkers = await listOpenApiMarkersForVersion( + client, + syncSchema, + markerColumn, + dataSchema, + apiVersion + ) + if (existingMarkers.includes(marker)) { + config.logger?.info({ marker }, 'OpenAPI schema already applied, skipping') + return + } + + // Backward compatibility: + // older branches stored marker suffixes as 40-char commit SHAs. Treat those as equivalent + // for GitHub/cache-resolved specs to prevent duplicate markers for the same API version. + if ( + resolvedSpec.source !== 'explicit_path' && + existingMarkers.some((existingMarker) => + isLegacyOpenApiCommitMarker(existingMarker, dataSchema, apiVersion) + ) + ) { + config.logger?.info( + { + marker, + existingMarkerCount: existingMarkers.length, + }, + 'OpenAPI schema already applied via legacy marker, skipping' + ) + return + } + const parser = new SpecParser() const parsedSpec = parser.parse(resolvedSpec.spec, { - resourceAliases: RUNTIME_RESOURCE_ALIASES, + resourceAliases: OPENAPI_RESOURCE_TABLE_ALIASES, allowedTables: [...RUNTIME_REQUIRED_TABLES], }) - const adapter = new PostgresAdapter({ schemaName: stripeSchemaName }) + const adapter = new PostgresAdapter({ + schemaName: dataSchema, + accountSchema: syncSchema, + }) const statements = adapter.buildAllStatements(parsedSpec.tables) for (const statement of statements) { - await client.query(statement) + await runSqlAdditive(client, statement, config.logger) } + await insertMigrationMarker(client, syncSchema, markerColumn, marker, fingerprint) + const planner = new WritePathPlanner() const writePlans = planner.buildPlans(parsedSpec.tables) config.logger?.info( { tableCount: parsedSpec.tables.length, writePlanCount: writePlans.length, + marker, }, 'Applied OpenAPI-generated Stripe tables' ) @@ -618,186 +522,43 @@ export async function runMigrations(config: MigrationConfig): Promise { ssl: config.ssl, connectionTimeoutMillis: 10_000, }) - const stripeSchemaName = config.schemaName ?? 'stripe' - - try { - await client.connect() - await rebuildStripeSchema(client, stripeSchemaName, config.logger) - await bootstrapInternalSchema(client, stripeSchemaName) - await applyOpenApiSchema(client, config, stripeSchemaName) - - if (config.enableSigma) { - await migrateSigmaSchema(client, config, 'sigma', stripeSchemaName) - } - } catch (err) { - config.logger?.error(err, 'Error running migrations') - throw err - } finally { - await client.end() - config.logger?.info('Finished migrations') - } -} - -// Helper to parse migration ID from filename (matches pg-node-migrations behavior) -function parseMigrationId(fileName: string): number { - const match = /^(-?\d+)[-_]?/.exec(fileName) - if (!match) { - throw new Error(`Invalid migration file name: '${fileName}'`) - } - return parseInt(match[1], 10) -} - -// Helper to compute hash matching pg-node-migrations format -function computeMigrationHash(fileName: string, sql: string): string { - return crypto - .createHash('sha1') - .update(fileName + sql, 'utf8') - .digest('hex') -} - -type ParsedMigration = { - id: number - name: string - fileName: string - sql: string - hash: string -} - -function parseMigrations(migrations: EmbeddedMigration[]): ParsedMigration[] { - return migrations - .map((m) => ({ - id: parseMigrationId(m.name), - name: m.name.replace(/^\d+[-_]?/, '').replace(/\.sql$/, '') || m.name, - fileName: m.name, - sql: m.sql, - hash: computeMigrationHash(m.name, m.sql), - })) - .sort((a, b) => a.id - b.id) -} - -async function ensureMigrationsTable( - client: Client, - schema: string, - tableName: string -): Promise { - await client.query(` - CREATE TABLE IF NOT EXISTS "${schema}"."${tableName}" ( - id integer PRIMARY KEY, - name varchar(100) UNIQUE NOT NULL, - hash varchar(40) NOT NULL, - executed_at timestamp DEFAULT current_timestamp - ) - `) -} - -async function getAppliedMigrations( - client: Client, - schema: string, - tableName: string -): Promise<{ id: number; name: string; hash: string }[]> { - const tableExists = await doesTableExist(client, schema, tableName) - if (!tableExists) { - return [] - } - const result = await client.query( - `SELECT id, name, hash FROM "${schema}"."${tableName}" ORDER BY id` - ) - return result.rows -} - -async function runMigration( - client: Client, - schema: string, - tableName: string, - migration: ParsedMigration, - logger?: Logger -): Promise { - logger?.info(`Running migration: ${migration.id} ${migration.name}`) - - await client.query('BEGIN') - try { - await client.query(migration.sql) - await client.query( - `INSERT INTO "${schema}"."${tableName}" (id, name, hash) VALUES ($1, $2, $3)`, - [migration.id, migration.name, migration.hash] + const dataSchema = config.schemaName ?? 'stripe' + const syncSchema = config.syncTablesSchemaName ?? dataSchema + const migrationsDirectory = path.resolve(__dirname, './migrations') + const defaultSchema = 'stripe' + + if (dataSchema !== defaultSchema || syncSchema !== defaultSchema) { + throw new Error( + `Custom schema migrations are no longer supported. Use "${defaultSchema}" for both schemaName and syncTablesSchemaName.` ) - await client.query('COMMIT') - } catch (err) { - await client.query('ROLLBACK') - throw err } -} - -/** - * Run migrations from embedded content (for use in edge functions without filesystem access). - * This is compatible with pg-node-migrations table format. - */ -export async function runMigrationsFromContent( - config: MigrationConfig, - migrations: EmbeddedMigration[] -): Promise { - const client = new Client({ - connectionString: config.databaseUrl, - ssl: config.ssl, - connectionTimeoutMillis: 10_000, - }) - - const schema = config.schemaName ?? 'stripe' - const tableName = '_migrations' try { - config.logger?.info('Starting migrations (from embedded content)') await client.connect() - config.logger?.info('Connected to database') - - // Ensure schema exists - await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schema)};`) + await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(defaultSchema)}`) + await renameMigrationsTableIfNeeded(client, syncSchema, config.logger) - // Rename old migrations table if it exists (one-time upgrade) - await renameMigrationsTableIfNeeded(client, schema, config.logger) - - // Check if migrations table is empty and cleanup if needed - const tableExists = await doesTableExist(client, schema, tableName) + const tableExists = await doesTableExist(client, syncSchema, '_migrations') if (tableExists) { const migrationCount = await client.query( - `SELECT COUNT(*) as count FROM "${schema}"."${tableName}"` + `SELECT COUNT(*) as count FROM "${syncSchema}"."_migrations"` ) const isEmpty = migrationCount.rows[0]?.count === '0' if (isEmpty) { - await cleanupSchema(client, schema, config.logger) + await cleanupSchema(client, syncSchema, config.logger) } } - // Ensure migrations table exists - await ensureMigrationsTable(client, schema, tableName) - - // Get applied migrations - const appliedMigrations = await getAppliedMigrations(client, schema, tableName) - const appliedIds = new Set(appliedMigrations.map((m) => m.id)) - - // Validate hashes of applied migrations match - const parsedMigrations = parseMigrations(migrations) - for (const applied of appliedMigrations) { - const intended = parsedMigrations.find((m) => m.id === applied.id) - if (intended && intended.hash !== applied.hash) { - throw new Error( - `Migration hash mismatch for ${applied.name}: ` + - `expected ${intended.hash}, got ${applied.hash}. ` + - `Migrations cannot be modified after being applied.` - ) - } + if (!fs.existsSync(migrationsDirectory)) { + throw new Error(`Migrations directory not found. ${migrationsDirectory} does not exist.`) } + config.logger?.info({ migrationsDirectory }, 'Running SQL migrations from directory') + await connectAndMigrate(client, migrationsDirectory, syncSchema, config, true) - // Run pending migrations - const pendingMigrations = parsedMigrations.filter((m) => !appliedIds.has(m.id)) - if (pendingMigrations.length === 0) { - config.logger?.info('No migrations to run') - } else { - config.logger?.info(`Running ${pendingMigrations.length} migration(s)`) - for (const migration of pendingMigrations) { - await runMigration(client, schema, tableName, migration, config.logger) - } - config.logger?.info(`Successfully applied ${pendingMigrations.length} migration(s)`) + await applyOpenApiSchema(client, config, dataSchema, syncSchema) + + if (config.enableSigma) { + await migrateSigmaSchema(client, config, 'sigma', syncSchema) } } catch (err) { config.logger?.error(err, 'Error running migrations') diff --git a/packages/sync-engine/src/database/migrations-embedded.ts b/packages/sync-engine/src/database/migrations-embedded.ts deleted file mode 100644 index ecfa7664..00000000 --- a/packages/sync-engine/src/database/migrations-embedded.ts +++ /dev/null @@ -1,286 +0,0 @@ -// AUTO-GENERATED by scripts/build-functions.ts — do not edit manually. -// Run `tsx scripts/build-functions.ts` (or the build) to regenerate. - -export type EmbeddedMigration = { - name: string - sql: string -} - -export const embeddedMigrations: EmbeddedMigration[] = [ - { - name: '0000_initial_migration.sql', - sql: 'select 1;', - }, - { - name: '0001_products.sql', - sql: 'create table if not exists "stripe"."products" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "description" text,\n "metadata" jsonb,\n "name" text,\n "created" integer,\n "images" jsonb,\n "livemode" boolean,\n "package_dimensions" jsonb,\n "shippable" boolean,\n "statement_descriptor" text,\n "unit_label" text,\n "updated" integer,\n "url" text\n);\n', - }, - { - name: '0002_customers.sql', - sql: 'create table if not exists "stripe"."customers" (\n "id" text primary key,\n "object" text,\n "address" jsonb,\n "description" text,\n "email" text,\n "metadata" jsonb,\n "name" text,\n "phone" text,\n "shipping" jsonb,\n "balance" integer,\n "created" integer,\n "currency" text,\n "default_source" text,\n "delinquent" boolean,\n "discount" jsonb,\n "invoice_prefix" text,\n "invoice_settings" jsonb,\n "livemode" boolean,\n "next_invoice_sequence" integer,\n "preferred_locales" jsonb,\n "tax_exempt" text\n);\n', - }, - { - name: '0003_prices.sql', - sql: 'DO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_type\') THEN\n create type "stripe"."pricing_type" as enum (\'one_time\', \'recurring\');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_tiers\') THEN\n create type "stripe"."pricing_tiers" as enum (\'graduated\', \'volume\');\n END IF;\n --more types here...\nEND\n$$;\n\n\ncreate table if not exists "stripe"."prices" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "currency" text,\n "metadata" jsonb,\n "nickname" text,\n "recurring" jsonb,\n "type" stripe.pricing_type,\n "unit_amount" integer,\n "billing_scheme" text,\n "created" integer,\n "livemode" boolean,\n "lookup_key" text,\n "tiers_mode" stripe.pricing_tiers,\n "transform_quantity" jsonb,\n "unit_amount_decimal" text,\n\n "product" text references stripe.products\n);\n\n', - }, - { - name: '0004_subscriptions.sql', - sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'subscription_status\') THEN\n create type "stripe"."subscription_status" as enum (\n \'trialing\',\n \'active\',\n \'canceled\',\n \'incomplete\',\n \'incomplete_expired\',\n \'past_due\',\n \'unpaid\'\n );\n END IF;\nEND\n$$;\n\ncreate table if not exists "stripe"."subscriptions" (\n "id" text primary key,\n "object" text,\n "cancel_at_period_end" boolean,\n "current_period_end" integer,\n "current_period_start" integer,\n "default_payment_method" text,\n "items" jsonb,\n "metadata" jsonb,\n "pending_setup_intent" text,\n "pending_update" jsonb,\n "status" "stripe"."subscription_status", \n "application_fee_percent" double precision,\n "billing_cycle_anchor" integer,\n "billing_thresholds" jsonb,\n "cancel_at" integer,\n "canceled_at" integer,\n "collection_method" text,\n "created" integer,\n "days_until_due" integer,\n "default_source" text,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "ended_at" integer,\n "livemode" boolean,\n "next_pending_invoice_item_invoice" integer,\n "pause_collection" jsonb,\n "pending_invoice_item_interval" jsonb,\n "start_date" integer,\n "transfer_data" jsonb,\n "trial_end" jsonb,\n "trial_start" jsonb,\n\n "schedule" text,\n "customer" text references "stripe"."customers",\n "latest_invoice" text, -- not yet joined\n "plan" text -- not yet joined\n);\n\n', - }, - { - name: '0005_invoices.sql', - sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'invoice_status\') THEN\n create type "stripe"."invoice_status" as enum (\'draft\', \'open\', \'paid\', \'uncollectible\', \'void\');\n END IF;\nEND\n$$;\n\n\ncreate table if not exists "stripe"."invoices" (\n "id" text primary key,\n "object" text,\n "auto_advance" boolean,\n "collection_method" text,\n "currency" text,\n "description" text,\n "hosted_invoice_url" text,\n "lines" jsonb,\n "metadata" jsonb,\n "period_end" integer,\n "period_start" integer,\n "status" "stripe"."invoice_status",\n "total" bigint,\n "account_country" text,\n "account_name" text,\n "account_tax_ids" jsonb,\n "amount_due" bigint,\n "amount_paid" bigint,\n "amount_remaining" bigint,\n "application_fee_amount" bigint,\n "attempt_count" integer,\n "attempted" boolean,\n "billing_reason" text,\n "created" integer,\n "custom_fields" jsonb,\n "customer_address" jsonb,\n "customer_email" text,\n "customer_name" text,\n "customer_phone" text,\n "customer_shipping" jsonb,\n "customer_tax_exempt" text,\n "customer_tax_ids" jsonb,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "discounts" jsonb,\n "due_date" integer,\n "ending_balance" integer,\n "footer" text,\n "invoice_pdf" text,\n "last_finalization_error" jsonb,\n "livemode" boolean,\n "next_payment_attempt" integer,\n "number" text,\n "paid" boolean,\n "payment_settings" jsonb,\n "post_payment_credit_notes_amount" integer,\n "pre_payment_credit_notes_amount" integer,\n "receipt_number" text,\n "starting_balance" integer,\n "statement_descriptor" text,\n "status_transitions" jsonb,\n "subtotal" integer,\n "tax" integer,\n "total_discount_amounts" jsonb,\n "total_tax_amounts" jsonb,\n "transfer_data" jsonb,\n "webhooks_delivered_at" integer,\n\n "customer" text references "stripe"."customers",\n "subscription" text references "stripe"."subscriptions",\n "payment_intent" text, -- not yet implemented\n "default_payment_method" text, -- not yet implemented\n "default_source" text, -- not yet implemented\n "on_behalf_of" text, -- not yet implemented\n "charge" text -- not yet implemented\n);\n', - }, - { - name: '0006_charges.sql', - sql: '\ncreate table if not exists "stripe".charges (\n id text primary key,\n object text,\n card jsonb,\n paid boolean,\n "order" text,\n amount bigint,\n review text,\n source jsonb,\n status text,\n created integer,\n dispute text,\n invoice text,\n outcome jsonb,\n refunds jsonb,\n updated integer,\n captured boolean,\n currency text,\n customer text,\n livemode boolean,\n metadata jsonb,\n refunded boolean,\n shipping jsonb,\n application text,\n description text,\n destination text,\n failure_code text,\n on_behalf_of text,\n fraud_details jsonb,\n receipt_email text,\n payment_intent text,\n receipt_number text,\n transfer_group text,\n amount_refunded bigint,\n application_fee text,\n failure_message text,\n source_transfer text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n payment_method_details jsonb\n);\n', - }, - { - name: '0007_coupons.sql', - sql: 'create table if not exists "stripe".coupons (\n id text primary key,\n object text,\n name text,\n valid boolean,\n created integer,\n updated integer,\n currency text,\n duration text,\n livemode boolean,\n metadata jsonb,\n redeem_by integer,\n amount_off bigint,\n percent_off double precision,\n times_redeemed bigint,\n max_redemptions bigint,\n duration_in_months bigint,\n percent_off_precise double precision\n);\n', - }, - { - name: '0008_disputes.sql', - sql: 'create table if not exists "stripe".disputes (\n id text primary key,\n object text,\n amount bigint,\n charge text,\n reason text,\n status text,\n created integer,\n updated integer,\n currency text,\n evidence jsonb,\n livemode boolean,\n metadata jsonb,\n evidence_details jsonb,\n balance_transactions jsonb,\n is_charge_refundable boolean\n);\n', - }, - { - name: '0009_events.sql', - sql: 'create table if not exists "stripe".events (\n id text primary key,\n object text,\n data jsonb,\n type text,\n created integer,\n request text,\n updated integer,\n livemode boolean,\n api_version text,\n pending_webhooks bigint\n);\n', - }, - { - name: '0010_payouts.sql', - sql: 'create table if not exists "stripe".payouts (\n id text primary key,\n object text,\n date text,\n type text,\n amount bigint,\n method text,\n status text,\n created integer,\n updated integer,\n currency text,\n livemode boolean,\n metadata jsonb,\n automatic boolean,\n recipient text,\n description text,\n destination text,\n source_type text,\n arrival_date text,\n bank_account jsonb,\n failure_code text,\n transfer_group text,\n amount_reversed bigint,\n failure_message text,\n source_transaction text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n failure_balance_transaction text\n);\n', - }, - { - name: '0011_plans.sql', - sql: 'create table if not exists "stripe"."plans" (\n id text primary key,\n object text,\n name text,\n tiers jsonb,\n active boolean,\n amount bigint,\n created integer,\n product text,\n updated integer,\n currency text,\n "interval" text,\n livemode boolean,\n metadata jsonb,\n nickname text,\n tiers_mode text,\n usage_type text,\n billing_scheme text,\n interval_count bigint,\n aggregate_usage text,\n transform_usage text,\n trial_period_days bigint,\n statement_descriptor text,\n statement_description text\n);\n', - }, - { - name: '0012_add_updated_at.sql', - sql: "create or replace function set_updated_at() returns trigger\n language plpgsql\nas\n$$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\nalter table stripe.subscriptions\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.subscriptions\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.products\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.products\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.customers\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.customers\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.prices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.prices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.invoices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.invoices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.charges\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.charges\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.coupons\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.coupons\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.disputes\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.disputes\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.events\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.events\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.payouts\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.payouts\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.plans\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.plans\n for each row\n execute procedure set_updated_at();\n", - }, - { - name: '0013_add_subscription_items.sql', - sql: 'create table if not exists "stripe"."subscription_items" (\n "id" text primary key,\n "object" text,\n "billing_thresholds" jsonb,\n "created" integer,\n "deleted" boolean,\n "metadata" jsonb,\n "quantity" integer,\n "price" text references "stripe"."prices",\n "subscription" text references "stripe"."subscriptions",\n "tax_rates" jsonb\n);', - }, - { - name: '0014_migrate_subscription_items.sql', - sql: 'WITH subscriptions AS (\n select jsonb_array_elements(items->\'data\') as obj from "stripe"."subscriptions"\n)\ninsert into "stripe"."subscription_items"\nselect obj->>\'id\' as "id",\n obj->>\'object\' as "object", \n obj->\'billing_thresholds\' as "billing_thresholds", \n (obj->>\'created\')::INTEGER as "created", \n (obj->>\'deleted\')::BOOLEAN as "deleted", \n obj->\'metadata\' as "metadata", \n (obj->>\'quantity\')::INTEGER as "quantity", \n (obj->\'price\'->>\'id\')::TEXT as "price", \n obj->>\'subscription\' as "subscription", \n obj->\'tax_rates\' as "tax_rates"\nfrom subscriptions\non conflict ("id") \ndo update set "id" = excluded."id",\n "object" = excluded."object",\n "billing_thresholds" = excluded."billing_thresholds",\n "created" = excluded."created",\n "deleted" = excluded."deleted",\n "metadata" = excluded."metadata",\n "quantity" = excluded."quantity",\n "price" = excluded."price",\n "subscription" = excluded."subscription",\n "tax_rates" = excluded."tax_rates"', - }, - { - name: '0015_add_customer_deleted.sql', - sql: 'alter table stripe.customers\n add deleted boolean default false not null;', - }, - { - name: '0016_add_invoice_indexes.sql', - sql: 'CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);', - }, - { - name: '0017_drop_charges_unavailable_columns.sql', - sql: '-- drop columns that are duplicated / not available anymore\n-- card is not available on webhook v.2020-03-02. We can get the detail from payment_method_details\n-- statement_description is not available on webhook v.2020-03-02\nalter table "stripe"."charges"\n drop column if exists "card",\n drop column if exists "statement_description";', - }, - { - name: '0018_setup_intents.sql', - sql: 'create table if not exists "stripe"."setup_intents" (\n id text primary key,\n object text,\n created integer,\n customer text,\n description text,\n payment_method text,\n status text,\n usage text,\n cancellation_reason text,\n latest_attempt text,\n mandate text,\n single_use_mandate text,\n on_behalf_of text\n);\n\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);', - }, - { - name: '0019_payment_methods.sql', - sql: 'create table if not exists "stripe"."payment_methods" (\n id text primary key,\n object text,\n created integer,\n customer text,\n type text,\n billing_details jsonb,\n metadata jsonb,\n card jsonb\n);\n\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);', - }, - { - name: '0020_disputes_payment_intent_created_idx.sql', - sql: 'ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS payment_intent TEXT;\n\nCREATE INDEX IF NOT EXISTS stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);', - }, - { - name: '0021_payment_intent.sql', - sql: 'create table if not exists "stripe"."payment_intents" (\n id text primary key,\n object text,\n amount integer,\n amount_capturable integer,\n amount_details jsonb,\n amount_received integer,\n application text,\n application_fee_amount integer,\n automatic_payment_methods text,\n canceled_at integer,\n cancellation_reason text,\n capture_method text,\n client_secret text,\n confirmation_method text,\n created integer,\n currency text,\n customer text,\n description text,\n invoice text,\n last_payment_error text,\n livemode boolean,\n metadata jsonb,\n next_action text,\n on_behalf_of text,\n payment_method text,\n payment_method_options jsonb,\n payment_method_types jsonb,\n processing text,\n receipt_email text,\n review text,\n setup_future_usage text,\n shipping jsonb,\n statement_descriptor text,\n statement_descriptor_suffix text,\n status text,\n transfer_data jsonb,\n transfer_group text\n);\n\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);', - }, - { - name: '0022_adjust_plans.sql', - sql: 'ALTER TABLE if exists "stripe"."plans" DROP COLUMN name;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN updated;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN tiers;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_descriptor;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_description;', - }, - { - name: '0023_invoice_deleted.sql', - sql: 'ALTER TYPE "stripe"."invoice_status" ADD VALUE \'deleted\';', - }, - { - name: '0024_subscription_schedules.sql', - sql: "do $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'subscription_schedule_status') THEN\n create type \"stripe\".\"subscription_schedule_status\" as enum ('not_started', 'active', 'completed', 'released', 'canceled');\n END IF;\nEND\n$$;\n\ncreate table if not exists\n \"stripe\".\"subscription_schedules\" (\n id text primary key,\n object text,\n application text,\n canceled_at integer,\n completed_at integer,\n created integer not null,\n current_phase jsonb,\n customer text not null,\n default_settings jsonb,\n end_behavior text,\n livemode boolean not null,\n metadata jsonb not null,\n phases jsonb not null,\n released_at integer,\n released_subscription text,\n status stripe.subscription_schedule_status not null,\n subscription text,\n test_clock text\n );", - }, - { - name: '0025_tax_ids.sql', - sql: 'create table if not exists\n "stripe"."tax_ids" (\n "id" text primary key,\n "object" text,\n "country" text,\n "customer" text,\n "type" text,\n "value" text,\n "created" integer not null,\n "livemode" boolean,\n "owner" jsonb\n );\n\ncreate index stripe_tax_ids_customer_idx on "stripe"."tax_ids" using btree (customer);', - }, - { - name: '0026_credit_notes.sql', - sql: 'create table if not exists\n "stripe"."credit_notes" (\n "id" text primary key,\n object text,\n amount integer,\n amount_shipping integer,\n created integer,\n currency text,\n customer text,\n customer_balance_transaction text,\n discount_amount integer,\n discount_amounts jsonb,\n invoice text,\n lines jsonb,\n livemode boolean,\n memo text,\n metadata jsonb,\n number text,\n out_of_band_amount integer,\n pdf text,\n reason text,\n refund text,\n shipping_cost jsonb,\n status text,\n subtotal integer,\n subtotal_excluding_tax integer,\n tax_amounts jsonb,\n total integer,\n total_excluding_tax integer,\n type text,\n voided_at text\n );\n\ncreate index stripe_credit_notes_customer_idx on "stripe"."credit_notes" using btree (customer);\n\ncreate index stripe_credit_notes_invoice_idx on "stripe"."credit_notes" using btree (invoice);', - }, - { - name: '0027_add_marketing_features_to_products.sql', - sql: 'ALTER TABLE IF EXISTS stripe.products ADD COLUMN IF NOT EXISTS marketing_features JSONB;\n\n', - }, - { - name: '0028_early_fraud_warning.sql', - sql: 'create table\n if not exists "stripe"."early_fraud_warnings" (\n "id" text primary key,\n object text,\n actionable boolean,\n charge text,\n created integer,\n fraud_type text,\n livemode boolean,\n payment_intent text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_early_fraud_warnings_charge_idx on "stripe"."early_fraud_warnings" using btree (charge);\n\ncreate index stripe_early_fraud_warnings_payment_intent_idx on "stripe"."early_fraud_warnings" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.early_fraud_warnings\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0029_reviews.sql', - sql: 'create table\n if not exists "stripe"."reviews" (\n "id" text primary key,\n object text,\n billing_zip text,\n charge text,\n created integer,\n closed_reason text,\n livemode boolean,\n ip_address text,\n ip_address_location jsonb,\n open boolean,\n opened_reason text,\n payment_intent text,\n reason text,\n session text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_reviews_charge_idx on "stripe"."reviews" using btree (charge);\n\ncreate index stripe_reviews_payment_intent_idx on "stripe"."reviews" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.reviews\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0030_refunds.sql', - sql: 'create table\n if not exists "stripe"."refunds" (\n "id" text primary key,\n object text,\n amount integer,\n balance_transaction text,\n charge text,\n created integer,\n currency text,\n destination_details jsonb,\n metadata jsonb,\n payment_intent text,\n reason text,\n receipt_number text,\n source_transfer_reversal text,\n status text,\n transfer_reversal text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_refunds_charge_idx on "stripe"."refunds" using btree (charge);\n\ncreate index stripe_refunds_payment_intent_idx on "stripe"."refunds" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.refunds\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0031_add_default_price.sql', - sql: 'alter table "stripe"."products"\nadd column IF NOT EXISTS "default_price" text;\n', - }, - { - name: '0032_update_subscription_items.sql', - sql: 'ALTER TABLE "stripe"."subscription_items"\nADD COLUMN IF NOT EXISTS "current_period_end" integer,\nADD COLUMN IF NOT EXISTS "current_period_start" integer;\n', - }, - { - name: '0033_add_last_synced_at.sql', - sql: '-- Add last_synced_at column to all Stripe tables for tracking sync status\n\n-- Charges\nalter table "stripe"."charges"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Coupons\nalter table "stripe"."coupons"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Credit Notes\nalter table "stripe"."credit_notes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Customers\nalter table "stripe"."customers"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Disputes\nalter table "stripe"."disputes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Early Fraud Warnings\nalter table "stripe"."early_fraud_warnings"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Events\nalter table "stripe"."events"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Invoices\nalter table "stripe"."invoices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Intents\nalter table "stripe"."payment_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Methods\nalter table "stripe"."payment_methods"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payouts\nalter table "stripe"."payouts"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Plans\nalter table "stripe"."plans"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Prices\nalter table "stripe"."prices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Products\nalter table "stripe"."products"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Refunds\nalter table "stripe"."refunds"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Reviews\nalter table "stripe"."reviews"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Setup Intents\nalter table "stripe"."setup_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Items\nalter table "stripe"."subscription_items"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Schedules\nalter table "stripe"."subscription_schedules"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscriptions\nalter table "stripe"."subscriptions"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Tax IDs\nalter table "stripe"."tax_ids"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n', - }, - { - name: '0034_remove_foreign_keys.sql', - sql: '-- Remove all foreign key constraints\n\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS "subscriptions_customer_fkey";\n\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS "prices_product_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_customer_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_subscription_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_price_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_subscription_fkey";\n', - }, - { - name: '0035_checkout_sessions.sql', - sql: 'create table\n if not exists "stripe"."checkout_sessions" (\n "id" text primary key,\n "object" text,\n "adaptive_pricing" jsonb,\n "after_expiration" jsonb,\n "allow_promotion_codes" boolean,\n "amount_subtotal" integer,\n "amount_total" integer,\n "automatic_tax" jsonb,\n "billing_address_collection" text,\n "cancel_url" text,\n "client_reference_id" text,\n "client_secret" text,\n "collected_information" jsonb,\n "consent" jsonb,\n "consent_collection" jsonb,\n "created" integer,\n "currency" text,\n "currency_conversion" jsonb,\n "custom_fields" jsonb,\n "custom_text" jsonb,\n "customer" text,\n "customer_creation" text,\n "customer_details" jsonb,\n "customer_email" text,\n "discounts" jsonb,\n "expires_at" integer,\n "invoice" text,\n "invoice_creation" jsonb,\n "livemode" boolean,\n "locale" text,\n "metadata" jsonb,\n "mode" text,\n "optional_items" jsonb,\n "payment_intent" text,\n "payment_link" text,\n "payment_method_collection" text,\n "payment_method_configuration_details" jsonb,\n "payment_method_options" jsonb,\n "payment_method_types" jsonb,\n "payment_status" text,\n "permissions" jsonb,\n "phone_number_collection" jsonb,\n "presentment_details" jsonb,\n "recovered_from" text,\n "redirect_on_completion" text,\n "return_url" text,\n "saved_payment_method_options" jsonb,\n "setup_intent" text,\n "shipping_address_collection" jsonb,\n "shipping_cost" jsonb,\n "shipping_details" jsonb,\n "shipping_options" jsonb,\n "status" text,\n "submit_type" text,\n "subscription" text,\n "success_url" text,\n "tax_id_collection" jsonb,\n "total_details" jsonb,\n "ui_mode" text,\n "url" text,\n "wallet_options" jsonb,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_checkout_sessions_customer_idx on "stripe"."checkout_sessions" using btree (customer);\ncreate index stripe_checkout_sessions_subscription_idx on "stripe"."checkout_sessions" using btree (subscription);\ncreate index stripe_checkout_sessions_payment_intent_idx on "stripe"."checkout_sessions" using btree (payment_intent);\ncreate index stripe_checkout_sessions_invoice_idx on "stripe"."checkout_sessions" using btree (invoice);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_sessions\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0036_checkout_session_line_items.sql', - sql: 'create table if not exists "stripe"."checkout_session_line_items" (\n "id" text primary key,\n "object" text,\n "amount_discount" integer,\n "amount_subtotal" integer,\n "amount_tax" integer,\n "amount_total" integer,\n "currency" text,\n "description" text,\n "price" text references "stripe"."prices" on delete cascade,\n "quantity" integer,\n "checkout_session" text references "stripe"."checkout_sessions" on delete cascade,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n);\n\ncreate index stripe_checkout_session_line_items_session_idx on "stripe"."checkout_session_line_items" using btree (checkout_session);\ncreate index stripe_checkout_session_line_items_price_idx on "stripe"."checkout_session_line_items" using btree (price);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_session_line_items\n for each row\n execute procedure set_updated_at(); ', - }, - { - name: '0037_add_features.sql', - sql: 'create table\n if not exists "stripe"."features" (\n "id" text primary key,\n object text,\n livemode boolean,\n name text,\n lookup_key text unique,\n active boolean,\n metadata jsonb,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null,\n last_synced_at timestamptz\n );\n\ncreate trigger handle_updated_at\n before update\n on stripe.features\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0038_active_entitlement.sql', - sql: 'create table\n if not exists "stripe"."active_entitlements" (\n "id" text primary key,\n "object" text,\n "livemode" boolean,\n "feature" text,\n "customer" text,\n "lookup_key" text unique,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_active_entitlements_customer_idx on "stripe"."active_entitlements" using btree (customer);\ncreate index stripe_active_entitlements_feature_idx on "stripe"."active_entitlements" using btree (feature);\n\ncreate trigger handle_updated_at\n before update\n on stripe.active_entitlements\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0039_add_paused_to_subscription_status.sql', - sql: 'ALTER TYPE "stripe"."subscription_status" ADD VALUE \'paused\';', - }, - { - name: '0040_managed_webhooks.sql', - sql: 'create table\n if not exists "stripe"."managed_webhooks" (\n "id" text primary key,\n "object" text,\n "uuid" text unique not null,\n "url" text not null,\n "enabled_events" jsonb not null,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text not null,\n "status" text,\n "api_version" text,\n "created" integer,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_managed_webhooks_uuid_idx on "stripe"."managed_webhooks" using btree (uuid);\ncreate index stripe_managed_webhooks_status_idx on "stripe"."managed_webhooks" using btree (status);\ncreate index stripe_managed_webhooks_enabled_idx on "stripe"."managed_webhooks" using btree (enabled);\n\ncreate trigger handle_updated_at\n before update\n on stripe.managed_webhooks\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0041_rename_managed_webhooks.sql', - sql: '-- Rename managed_webhooks table to _managed_webhooks\nalter table if exists "stripe"."managed_webhooks" rename to "_managed_webhooks";\n', - }, - { - name: '0042_convert_to_jsonb_generated_columns.sql', - sql: '-- Convert all tables to use jsonb raw_data as source of truth with generated columns\n-- This migration adds raw_data column and converts all existing columns to generated columns\n\n-- ============================================================================\n-- ACTIVE_ENTITLEMENTS\n-- ============================================================================\n\n-- Add raw_data column\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes (will be recreated on generated columns)\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_feature_idx";\n\n-- Drop unique constraint (will be recreated on generated column)\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS "active_entitlements_lookup_key_key";\n\n-- Drop existing columns and recreate as generated columns\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((raw_data->>\'feature\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- CHARGES\n-- ============================================================================\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((raw_data->>\'order\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (raw_data->\'source\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((raw_data->>\'dispute\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (raw_data->\'outcome\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (raw_data->\'refunds\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((raw_data->>\'captured\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((raw_data->>\'refunded\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (raw_data->\'fraud_details\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_refunded\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((raw_data->>\'application_fee\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_details\') STORED;\n\n-- ============================================================================\n-- CHECKOUT_SESSION_LINE_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_session_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_price_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((raw_data->>\'amount_discount\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((raw_data->>\'amount_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((raw_data->>\'checkout_session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\n\n-- ============================================================================\n-- CHECKOUT_SESSIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_subscription_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_payment_intent_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_invoice_idx";\n\n-- Drop and recreate columns as generated (all columns from checkoutSessionSchema)\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (raw_data->\'adaptive_pricing\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (raw_data->\'after_expiration\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((raw_data->>\'allow_promotion_codes\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (raw_data->\'automatic_tax\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((raw_data->>\'billing_address_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((raw_data->>\'cancel_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((raw_data->>\'client_reference_id\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (raw_data->\'collected_information\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (raw_data->\'consent\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (raw_data->\'consent_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (raw_data->\'currency_conversion\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (raw_data->\'custom_text\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((raw_data->>\'customer_creation\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (raw_data->\'customer_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((raw_data->>\'expires_at\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_creation\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((raw_data->>\'locale\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((raw_data->>\'mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (raw_data->\'optional_items\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((raw_data->>\'payment_link\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((raw_data->>\'payment_method_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_configuration_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((raw_data->>\'payment_status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (raw_data->\'permissions\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (raw_data->\'phone_number_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (raw_data->\'presentment_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((raw_data->>\'recovered_from\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((raw_data->>\'redirect_on_completion\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((raw_data->>\'return_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'saved_payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_address_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((raw_data->>\'submit_type\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((raw_data->>\'success_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (raw_data->\'tax_id_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (raw_data->\'total_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((raw_data->>\'ui_mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (raw_data->\'wallet_options\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\n\n-- ============================================================================\n-- COUPONS\n-- ============================================================================\n\nALTER TABLE "stripe"."coupons" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((raw_data->>\'valid\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((raw_data->>\'duration\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((raw_data->>\'redeem_by\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_off\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off\')::double precision) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((raw_data->>\'times_redeemed\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((raw_data->>\'max_redemptions\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((raw_data->>\'duration_in_months\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off_precise\')::double precision) STORED;\n\n-- ============================================================================\n-- CREDIT_NOTES\n-- ============================================================================\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((raw_data->>\'amount_shipping\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'customer_balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((raw_data->>\'discount_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'discount_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((raw_data->>\'memo\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((raw_data->>\'out_of_band_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((raw_data->>\'pdf\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((raw_data->>\'refund\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'tax_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((raw_data->>\'total\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'total_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((raw_data->>\'voided_at\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\n\n-- ============================================================================\n-- CUSTOMERS\n-- ============================================================================\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (raw_data->\'address\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((raw_data->>\'phone\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((raw_data->>\'balance\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((raw_data->>\'delinquent\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((raw_data->>\'invoice_prefix\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_settings\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((raw_data->>\'next_invoice_sequence\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (raw_data->\'preferred_locales\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\n-- ============================================================================\n-- DISPUTES\n-- ============================================================================\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_dispute_created_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (raw_data->\'evidence\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (raw_data->\'evidence_details\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (raw_data->\'balance_transactions\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((raw_data->>\'is_charge_refundable\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\n\n-- ============================================================================\n-- EARLY_FRAUD_WARNINGS\n-- ============================================================================\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((raw_data->>\'actionable\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((raw_data->>\'fraud_type\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\n\n-- ============================================================================\n-- EVENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."events" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (raw_data->\'data\') STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((raw_data->>\'request\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((raw_data->>\'api_version\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((raw_data->>\'pending_webhooks\')::bigint) STORED;\n\n-- ============================================================================\n-- FEATURES\n-- ============================================================================\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop unique constraint\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS "features_lookup_key_key";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- INVOICES\n-- ============================================================================\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_subscription_idx";\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((raw_data->>\'auto_advance\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((raw_data->>\'hosted_invoice_url\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((raw_data->>\'period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((raw_data->>\'period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((raw_data->>\'total\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((raw_data->>\'account_country\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((raw_data->>\'account_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'account_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_due\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_paid\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_remaining\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((raw_data->>\'attempt_count\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((raw_data->>\'attempted\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((raw_data->>\'billing_reason\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (raw_data->\'customer_address\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((raw_data->>\'customer_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((raw_data->>\'customer_phone\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (raw_data->\'customer_shipping\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'customer_tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'customer_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((raw_data->>\'due_date\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((raw_data->>\'ending_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((raw_data->>\'footer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((raw_data->>\'invoice_pdf\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (raw_data->\'last_finalization_error\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((raw_data->>\'next_payment_attempt\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (raw_data->\'payment_settings\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((raw_data->>\'starting_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (raw_data->\'status_transitions\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((raw_data->>\'tax\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_discount_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_tax_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((raw_data->>\'webhooks_delivered_at\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\n\n-- ============================================================================\n-- PAYMENT_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((raw_data->>\'amount_capturable\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (raw_data->\'amount_details\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((raw_data->>\'amount_received\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((raw_data->>\'automatic_payment_methods\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((raw_data->>\'capture_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((raw_data->>\'confirmation_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((raw_data->>\'last_payment_error\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((raw_data->>\'next_action\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((raw_data->>\'processing\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((raw_data->>\'setup_future_usage\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor_suffix\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\n\n-- ============================================================================\n-- PAYMENT_METHODS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_methods_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (raw_data->\'billing_details\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (raw_data->\'card\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\n\n-- ============================================================================\n-- PAYOUTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payouts" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((raw_data->>\'date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((raw_data->>\'method\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((raw_data->>\'automatic\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((raw_data->>\'recipient\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((raw_data->>\'source_type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((raw_data->>\'arrival_date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (raw_data->\'bank_account\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_reversed\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((raw_data->>\'source_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'failure_balance_transaction\')::text) STORED;\n\n-- ============================================================================\n-- PLANS\n-- ============================================================================\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (raw_data->\'tiers\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((raw_data->>\'interval\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((raw_data->>\'usage_type\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((raw_data->>\'interval_count\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((raw_data->>\'aggregate_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((raw_data->>\'transform_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((raw_data->>\'trial_period_days\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\n-- ============================================================================\n-- PRICES\n-- ============================================================================\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum types converted to text)\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (raw_data->\'recurring\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((raw_data->>\'unit_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (raw_data->\'transform_quantity\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((raw_data->>\'unit_amount_decimal\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\n-- ============================================================================\n-- PRODUCTS\n-- ============================================================================\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((raw_data->>\'default_price\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (raw_data->\'images\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (raw_data->\'marketing_features\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (raw_data->\'package_dimensions\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((raw_data->>\'shippable\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((raw_data->>\'unit_label\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\n-- ============================================================================\n-- REFUNDS\n-- ============================================================================\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (raw_data->\'destination_details\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer_reversal\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'transfer_reversal\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\n\n-- ============================================================================\n-- REVIEWS\n-- ============================================================================\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((raw_data->>\'billing_zip\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((raw_data->>\'closed_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((raw_data->>\'ip_address\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (raw_data->\'ip_address_location\') STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((raw_data->>\'open\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((raw_data->>\'opened_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((raw_data->>\'session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\n\n-- ============================================================================\n-- SETUP_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_setup_intents_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((raw_data->>\'usage\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((raw_data->>\'latest_attempt\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((raw_data->>\'mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((raw_data->>\'single_use_mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\n\n-- ============================================================================\n-- SUBSCRIPTION_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTION_SCHEDULES\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((raw_data->>\'completed_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (raw_data->\'current_phase\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (raw_data->\'default_settings\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((raw_data->>\'end_behavior\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (raw_data->\'phases\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((raw_data->>\'released_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((raw_data->>\'released_subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((raw_data->>\'test_clock\')::text) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((raw_data->>\'cancel_at_period_end\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (raw_data->\'items\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'pending_setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (raw_data->\'pending_update\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((raw_data->>\'application_fee_percent\')::double precision) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((raw_data->>\'billing_cycle_anchor\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((raw_data->>\'cancel_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((raw_data->>\'days_until_due\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((raw_data->>\'ended_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (raw_data->\'pause_collection\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (raw_data->\'pending_invoice_item_interval\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((raw_data->>\'start_date\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (raw_data->\'trial_end\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (raw_data->\'trial_start\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((raw_data->>\'schedule\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((raw_data->>\'latest_invoice\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((raw_data->>\'plan\')::text) STORED;\n\n-- ============================================================================\n-- TAX_IDS\n-- ============================================================================\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_tax_ids_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((raw_data->>\'value\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (raw_data->\'owner\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n\n', - }, - { - name: '0043_add_account_id.sql', - sql: '-- Add _account_id column to all tables to track which Stripe account each record belongs to\n-- Column is nullable for backward compatibility with existing data\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."_managed_webhooks" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\n', - }, - { - name: '0044_make_account_id_required.sql', - sql: '-- Make _account_id required by:\n-- 1. Deleting all rows where _account_id IS NULL\n-- 2. Setting _account_id to NOT NULL\n\n-- Delete rows with null _account_id\nDELETE FROM "stripe"."active_entitlements" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."charges" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_session_line_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_sessions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."credit_notes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."customers" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."disputes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."early_fraud_warnings" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."features" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."invoices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."_managed_webhooks" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_methods" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."plans" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."prices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."products" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."refunds" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."reviews" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."setup_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_schedules" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscriptions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."tax_ids" WHERE "_account_id" IS NULL;\n\n-- Make _account_id NOT NULL\nALTER TABLE "stripe"."active_entitlements" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."charges" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_session_line_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_sessions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."credit_notes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."customers" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."disputes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."early_fraud_warnings" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."features" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."invoices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."_managed_webhooks" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_methods" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."plans" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."prices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."products" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."refunds" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."reviews" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."setup_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_schedules" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscriptions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."tax_ids" ALTER COLUMN "_account_id" SET NOT NULL;\n\n', - }, - { - name: '0045_sync_status.sql', - sql: "-- Create _sync_status metadata table for tracking incremental sync cursors\n-- This table tracks the state and progress of each resource's synchronization\n\nCREATE TABLE IF NOT EXISTS \"stripe\".\"_sync_status\" (\n id serial PRIMARY KEY,\n resource text UNIQUE NOT NULL,\n status text CHECK (status IN ('idle', 'running', 'complete', 'error')) DEFAULT 'idle',\n last_synced_at timestamptz DEFAULT now(),\n last_incremental_cursor timestamptz,\n error_message text,\n updated_at timestamptz DEFAULT now()\n);\n\n-- Use existing set_updated_at() function created in migration 0012\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON \"stripe\".\"_sync_status\"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n", - }, - { - name: '0046_sync_status_per_account.sql', - sql: '-- Add _account_id to _sync_status table to track sync cursors per account\n-- This enables proper cursor isolation when syncing multiple Stripe accounts\n--\n-- Breaking change: All existing cursor data will be deleted (clean slate)\n-- Next sync will perform a full backfill for each account\n\n-- Step 1: Delete all existing cursor data\nDELETE FROM "stripe"."_sync_status";\n\n-- Step 2: Add _account_id column\nALTER TABLE "stripe"."_sync_status" ADD COLUMN "_account_id" TEXT NOT NULL;\n\n-- Step 3: Drop existing unique constraint on resource\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_key;\n\n-- Step 4: Add new composite unique constraint on (resource, _account_id)\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "_account_id");\n\n-- Step 5: Add index for efficient lookups\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "_account_id");\n\n-- Step 6: Create accounts table to track Stripe accounts (JSONB with generated columns)\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n id TEXT PRIMARY KEY,\n raw_data JSONB NOT NULL,\n first_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n -- Generated columns extracted from raw_data\n business_name TEXT GENERATED ALWAYS AS ((raw_data->\'business_profile\'->>\'name\')::text) STORED,\n email TEXT GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED,\n type TEXT GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED,\n charges_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'charges_enabled\')::boolean) STORED,\n payouts_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'payouts_enabled\')::boolean) STORED,\n details_submitted BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'details_submitted\')::boolean) STORED,\n country TEXT GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED,\n default_currency TEXT GENERATED ALWAYS AS ((raw_data->>\'default_currency\')::text) STORED,\n created INTEGER GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED\n);\n\n-- Step 7: Add index for account name lookups\nCREATE INDEX IF NOT EXISTS idx_accounts_business_name\n ON "stripe"."accounts" (business_name);\n\n-- Step 8: Add updated_at trigger for accounts\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."accounts"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n\n-- Step 9: Backfill accounts from existing data tables\nINSERT INTO "stripe"."accounts" (id, raw_data, first_synced_at, last_synced_at)\nSELECT DISTINCT\n "_account_id" as id,\n jsonb_build_object(\'id\', "_account_id", \'type\', \'unknown\') as raw_data,\n now() as first_synced_at,\n now() as last_synced_at\nFROM "stripe"."products"\nWHERE "_account_id" IS NOT NULL\nON CONFLICT (id) DO NOTHING;\n\n-- Step 10: Add foreign key constraints from data tables to accounts\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Step 11: Add foreign key from _sync_status to accounts\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n', - }, - { - name: '0047_api_key_hashes.sql', - sql: '-- Add api_key_hashes array column to accounts table\n-- This stores SHA-256 hashes of Stripe API keys for fast account lookups\n-- Enables lookup of account ID by API key hash without making Stripe API calls\n-- Supports multiple API keys per account (test/live keys, rotated keys, etc.)\n\n-- Step 1: Add api_key_hashes column as TEXT array\nALTER TABLE "stripe"."accounts" ADD COLUMN "api_key_hashes" TEXT[] DEFAULT \'{}\';\n\n-- Step 2: Create GIN index for fast array containment lookups\n-- This enables efficient queries like: WHERE \'hash_value\' = ANY(api_key_hashes)\nCREATE INDEX IF NOT EXISTS idx_accounts_api_key_hashes\n ON "stripe"."accounts" USING GIN (api_key_hashes);\n', - }, - { - name: '0048_rename_reserved_columns.sql', - sql: '-- Rename all reserved column names to use underscore prefix\n-- This clearly distinguishes system-managed columns from user-accessible data\n--\n-- Changes:\n-- - id -> _id (primary key for all tables)\n-- - raw_data -> _raw_data (JSONB source of truth)\n-- - last_synced_at -> _last_synced_at (sync timestamp)\n-- - updated_at -> _updated_at (last update timestamp)\n-- - _account_id remains unchanged (already has underscore prefix)\n\n-- ============================================================================\n-- STEP 1: RENAME BASE COLUMNS FOR ALL TABLES\n-- ============================================================================\n\n-- This renames id, raw_data, last_synced_at, and updated_at for all tables\n-- PostgreSQL automatically updates primary keys, foreign keys, and indexes\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- charges\nALTER TABLE "stripe"."charges" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."charges" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."charges" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."charges" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- coupons\nALTER TABLE "stripe"."coupons" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- customers\nALTER TABLE "stripe"."customers" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."customers" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."customers" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."customers" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- disputes\nALTER TABLE "stripe"."disputes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- events\nALTER TABLE "stripe"."events" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."events" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."events" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."events" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- features\nALTER TABLE "stripe"."features" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."features" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."features" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."features" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- invoices\nALTER TABLE "stripe"."invoices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- _managed_webhooks\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payouts\nALTER TABLE "stripe"."payouts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- plans\nALTER TABLE "stripe"."plans" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."plans" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."plans" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."plans" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- prices\nALTER TABLE "stripe"."prices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."prices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."prices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."prices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- products\nALTER TABLE "stripe"."products" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."products" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."products" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."products" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- refunds\nALTER TABLE "stripe"."refunds" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- reviews\nALTER TABLE "stripe"."reviews" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- Metadata Tables\n\n-- _sync_status\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- accounts\nALTER TABLE "stripe"."accounts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- ============================================================================\n-- STEP 1.5: UPDATE TRIGGER FUNCTION TO USE NEW COLUMN NAME\n-- ============================================================================\n\n-- Now that all columns are renamed, update the trigger function to reference _updated_at\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new._updated_at = now();\n return NEW;\nend;\n$$;\n\n-- ============================================================================\n-- STEP 2: RECREATE GENERATED COLUMNS TO REFERENCE _raw_data\n-- ============================================================================\n\n-- All generated columns must be dropped and recreated to reference the new\n-- _raw_data column name instead of raw_data\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\n\n-- Add generated columns back with _raw_data references\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((_raw_data->>\'feature\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((_raw_data->>\'order\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (_raw_data->\'source\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((_raw_data->>\'dispute\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (_raw_data->\'outcome\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (_raw_data->\'refunds\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((_raw_data->>\'captured\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((_raw_data->>\'refunded\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (_raw_data->\'fraud_details\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_refunded\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((_raw_data->>\'application_fee\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_details\') STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((_raw_data->>\'checkout_session\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (_raw_data->\'adaptive_pricing\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (_raw_data->\'after_expiration\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((_raw_data->>\'allow_promotion_codes\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (_raw_data->\'automatic_tax\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((_raw_data->>\'billing_address_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((_raw_data->>\'cancel_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((_raw_data->>\'client_reference_id\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (_raw_data->\'collected_information\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (_raw_data->\'consent\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'consent_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (_raw_data->\'currency_conversion\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_text\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((_raw_data->>\'customer_creation\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_creation\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((_raw_data->>\'locale\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((_raw_data->>\'mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (_raw_data->\'optional_items\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((_raw_data->>\'payment_link\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_configuration_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((_raw_data->>\'payment_status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (_raw_data->\'permissions\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'phone_number_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (_raw_data->\'presentment_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((_raw_data->>\'recovered_from\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((_raw_data->>\'redirect_on_completion\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((_raw_data->>\'return_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'saved_payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'setup_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_address_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((_raw_data->>\'submit_type\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((_raw_data->>\'success_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_id_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (_raw_data->\'total_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((_raw_data->>\'ui_mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (_raw_data->\'wallet_options\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((_raw_data->>\'valid\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((_raw_data->>\'duration\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((_raw_data->>\'redeem_by\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_off\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off\')::double precision) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((_raw_data->>\'times_redeemed\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((_raw_data->>\'max_redemptions\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((_raw_data->>\'duration_in_months\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off_precise\')::double precision) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'customer_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discount_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((_raw_data->>\'memo\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((_raw_data->>\'pdf\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((_raw_data->>\'refund\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((_raw_data->>\'total\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((_raw_data->>\'voided_at\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (_raw_data->\'address\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((_raw_data->>\'email\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((_raw_data->>\'phone\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((_raw_data->>\'balance\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((_raw_data->>\'delinquent\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_prefix\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_settings\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((_raw_data->>\'next_invoice_sequence\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (_raw_data->\'preferred_locales\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence_details\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (_raw_data->\'balance_transactions\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((_raw_data->>\'is_charge_refundable\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((_raw_data->>\'actionable\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((_raw_data->>\'fraud_type\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (_raw_data->\'data\') STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((_raw_data->>\'request\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((_raw_data->>\'api_version\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((_raw_data->>\'pending_webhooks\')::bigint) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((_raw_data->>\'auto_advance\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((_raw_data->>\'hosted_invoice_url\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'period_end\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'period_start\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((_raw_data->>\'account_country\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((_raw_data->>\'account_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'account_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_due\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_paid\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_remaining\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((_raw_data->>\'attempt_count\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((_raw_data->>\'attempted\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((_raw_data->>\'billing_reason\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_address\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((_raw_data->>\'customer_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((_raw_data->>\'customer_phone\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_shipping\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'customer_tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((_raw_data->>\'due_date\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((_raw_data->>\'footer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_pdf\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (_raw_data->\'last_finalization_error\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((_raw_data->>\'next_payment_attempt\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_settings\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (_raw_data->\'status_transitions\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((_raw_data->>\'tax\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_discount_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_tax_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((_raw_data->>\'webhooks_delivered_at\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (_raw_data->\'amount_details\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((_raw_data->>\'automatic_payment_methods\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((_raw_data->>\'capture_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((_raw_data->>\'confirmation_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((_raw_data->>\'last_payment_error\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((_raw_data->>\'next_action\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((_raw_data->>\'processing\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((_raw_data->>\'setup_future_usage\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor_suffix\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_details\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (_raw_data->\'card\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((_raw_data->>\'date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((_raw_data->>\'method\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((_raw_data->>\'automatic\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((_raw_data->>\'recipient\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((_raw_data->>\'source_type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((_raw_data->>\'arrival_date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (_raw_data->\'bank_account\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_reversed\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'source_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'failure_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (_raw_data->\'tiers\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((_raw_data->>\'interval\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((_raw_data->>\'usage_type\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((_raw_data->>\'interval_count\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((_raw_data->>\'aggregate_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((_raw_data->>\'transform_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((_raw_data->>\'trial_period_days\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (_raw_data->\'recurring\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (_raw_data->\'transform_quantity\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((_raw_data->>\'unit_amount_decimal\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((_raw_data->>\'default_price\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (_raw_data->\'images\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (_raw_data->\'marketing_features\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (_raw_data->\'package_dimensions\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((_raw_data->>\'shippable\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((_raw_data->>\'unit_label\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (_raw_data->\'destination_details\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((_raw_data->>\'billing_zip\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((_raw_data->>\'closed_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((_raw_data->>\'ip_address\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (_raw_data->\'ip_address_location\') STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((_raw_data->>\'open\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((_raw_data->>\'opened_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((_raw_data->>\'session\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((_raw_data->>\'usage\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((_raw_data->>\'latest_attempt\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((_raw_data->>\'mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((_raw_data->>\'single_use_mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_rates\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((_raw_data->>\'completed_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (_raw_data->\'current_phase\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'default_settings\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((_raw_data->>\'end_behavior\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (_raw_data->\'phases\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((_raw_data->>\'released_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((_raw_data->>\'released_subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((_raw_data->>\'test_clock\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((_raw_data->>\'cancel_at_period_end\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (_raw_data->\'items\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'pending_setup_intent\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_update\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((_raw_data->>\'application_fee_percent\')::double precision) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((_raw_data->>\'billing_cycle_anchor\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((_raw_data->>\'cancel_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((_raw_data->>\'days_until_due\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((_raw_data->>\'ended_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((_raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'pause_collection\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_invoice_item_interval\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((_raw_data->>\'start_date\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_end\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_start\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((_raw_data->>\'schedule\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((_raw_data->>\'latest_invoice\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((_raw_data->>\'plan\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((_raw_data->>\'country\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((_raw_data->>\'value\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (_raw_data->\'owner\') STORED;\n\n-- ============================================================================\n-- STEP 3: RECREATE INDEXES\n-- ============================================================================\n\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n', - }, - { - name: '0049_remove_redundant_underscores_from_metadata_tables.sql', - sql: '-- Remove redundant underscore prefixes from columns in metadata tables\n--\n-- For tables that are already prefixed with underscore (indicating they are\n-- metadata/system tables), the underscore prefix on columns is redundant.\n-- This migration removes those redundant prefixes to keep naming cleaner.\n--\n-- Affected tables: _sync_status, _managed_webhooks\n\n-- Create a new trigger function for metadata tables that references updated_at without underscore\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\n-- Update _sync_status table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_status";\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_account_key;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\nDROP INDEX IF EXISTS "stripe"."idx_sync_status_resource_account";\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate constraints and trigger with new column names\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "account_id");\n\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "account_id");\n\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT fk_sync_status_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_status"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Update _managed_webhooks table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate foreign key constraint and trigger with new column names\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT fk_managed_webhooks_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_managed_webhooks"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n', - }, - { - name: '0050_rename_id_to_match_stripe_api.sql', - sql: '-- Rename _id back to id to match Stripe API field names\n--\n-- Migration 0048 added underscore prefixes to all "reserved" columns including id.\n-- However, id is actually a field that comes directly from the Stripe API and should\n-- match the API naming for agent/user comprehension.\n--\n-- Additionally, this migration converts id from a regular column to a GENERATED column\n-- derived from _raw_data->>\'id\', ensuring the raw_data is the single source of truth.\n\n-- ============================================================================\n-- Step 1: Drop all foreign key constraints referencing accounts._id\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS fk_active_entitlements_account;\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS fk_charges_account;\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS fk_checkout_session_line_items_account;\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS fk_checkout_sessions_account;\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS fk_credit_notes_account;\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS fk_customers_account;\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS fk_disputes_account;\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS fk_early_fraud_warnings_account;\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS fk_features_account;\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS fk_invoices_account;\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS fk_payment_intents_account;\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS fk_payment_methods_account;\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS fk_plans_account;\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS fk_prices_account;\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS fk_products_account;\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS fk_refunds_account;\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS fk_reviews_account;\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS fk_setup_intents_account;\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS fk_subscription_items_account;\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS fk_subscription_schedules_account;\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS fk_subscriptions_account;\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS fk_tax_ids_account;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\n\n-- ============================================================================\n-- Step 2: Convert accounts._id to generated column accounts.id\n-- ============================================================================\n\nALTER TABLE "stripe"."accounts" DROP CONSTRAINT IF EXISTS accounts_pkey;\nALTER TABLE "stripe"."accounts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."accounts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."accounts" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 3: Convert _id to generated column id for all Stripe entity tables\n-- ============================================================================\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS active_entitlements_pkey;\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD PRIMARY KEY (id);\n\n-- charges\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS charges_pkey;\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."charges" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."charges" ADD PRIMARY KEY (id);\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS checkout_session_line_items_pkey;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD PRIMARY KEY (id);\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS checkout_sessions_pkey;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD PRIMARY KEY (id);\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS credit_notes_pkey;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."credit_notes" ADD PRIMARY KEY (id);\n\n-- coupons\nALTER TABLE "stripe"."coupons" DROP CONSTRAINT IF EXISTS coupons_pkey;\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."coupons" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."coupons" ADD PRIMARY KEY (id);\n\n-- customers\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS customers_pkey;\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."customers" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."customers" ADD PRIMARY KEY (id);\n\n-- disputes\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS disputes_pkey;\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."disputes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."disputes" ADD PRIMARY KEY (id);\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS early_fraud_warnings_pkey;\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD PRIMARY KEY (id);\n\n-- events\nALTER TABLE "stripe"."events" DROP CONSTRAINT IF EXISTS events_pkey;\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."events" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."events" ADD PRIMARY KEY (id);\n\n-- features\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS features_pkey;\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."features" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."features" ADD PRIMARY KEY (id);\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS invoices_pkey;\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."invoices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."invoices" ADD PRIMARY KEY (id);\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS payment_intents_pkey;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_intents" ADD PRIMARY KEY (id);\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS payment_methods_pkey;\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_methods" ADD PRIMARY KEY (id);\n\n-- payouts\nALTER TABLE "stripe"."payouts" DROP CONSTRAINT IF EXISTS payouts_pkey;\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payouts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payouts" ADD PRIMARY KEY (id);\n\n-- plans\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS plans_pkey;\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."plans" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."plans" ADD PRIMARY KEY (id);\n\n-- prices\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS prices_pkey;\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."prices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."prices" ADD PRIMARY KEY (id);\n\n-- products\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS products_pkey;\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."products" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."products" ADD PRIMARY KEY (id);\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS refunds_pkey;\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."refunds" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."refunds" ADD PRIMARY KEY (id);\n\n-- reviews\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS reviews_pkey;\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."reviews" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."reviews" ADD PRIMARY KEY (id);\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS setup_intents_pkey;\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."setup_intents" ADD PRIMARY KEY (id);\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS subscription_items_pkey;\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_items" ADD PRIMARY KEY (id);\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS subscription_schedules_pkey;\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD PRIMARY KEY (id);\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS subscriptions_pkey;\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscriptions" ADD PRIMARY KEY (id);\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS tax_ids_pkey;\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."tax_ids" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 4: Handle metadata tables\n-- ============================================================================\n\n-- _managed_webhooks (internal metadata table, doesn\'t use _raw_data pattern)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- _sync_status (internal table, uses auto-incrementing id not from Stripe)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- ============================================================================\n-- Step 5: Recreate all foreign key constraints\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\n', - }, - { - name: '0051_remove_webhook_uuid.sql', - sql: '-- Remove UUID from managed webhooks\n-- UUID-based routing is no longer used; webhooks are identified by exact URL match\n-- Legacy webhooks with UUID in URL will be automatically deleted and recreated\n\ndrop index if exists "stripe"."stripe_managed_webhooks_uuid_idx";\n\nalter table "stripe"."_managed_webhooks" drop column if exists "uuid";\n', - }, - { - name: '0052_webhook_url_uniqueness.sql', - sql: '-- Add unique constraint on URL per account to prevent duplicate webhooks at database level\n-- This prevents race conditions where multiple instances try to create webhooks for the same URL\n-- Since UUIDs have been removed from URLs, we can enforce strict uniqueness on the URL column per account\n-- Note: Different accounts can have webhooks with the same URL\n\nalter table "stripe"."_managed_webhooks"\n add constraint managed_webhooks_url_account_unique unique ("url", "account_id");\n', - }, - { - name: '0053_sync_observability.sql', - sql: '-- Observable Sync System: Track sync runs and individual object syncs\n-- Enables observability for long-running syncs (days, not minutes)\n--\n-- Two-level hierarchy:\n-- _sync_run: Parent sync operation (one active per account)\n-- _sync_obj_run: Individual object syncs within a run\n--\n-- Features:\n-- - Only one active run per account (EXCLUDE constraint)\n-- - Configurable object concurrency (max_concurrent)\n-- - Stale detection (is_stale in dashboard view)\n-- - Progress tracking per object\n\n-- Step 1: Create _sync_run table (parent sync operation)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_run" (\n "_account_id" TEXT NOT NULL,\n started_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n status TEXT NOT NULL DEFAULT \'running\' CHECK (status IN (\'running\', \'complete\', \'error\')),\n max_concurrent INTEGER NOT NULL DEFAULT 3,\n completed_at TIMESTAMPTZ,\n error_message TEXT,\n triggered_by TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", started_at),\n\n -- Only one active run per account\n CONSTRAINT one_active_run_per_account\n EXCLUDE ("_account_id" WITH =) WHERE (status = \'running\'),\n\n -- Foreign key to accounts table\n CONSTRAINT fk_sync_run_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)\n);\n\n-- Step 2: Add updated_at trigger for _sync_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 3: Create _sync_obj_run table (individual object syncs)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_run" (\n "_account_id" TEXT NOT NULL,\n run_started_at TIMESTAMPTZ NOT NULL,\n object TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT \'pending\' CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n processed_count INTEGER DEFAULT 0,\n cursor TEXT,\n error_message TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", run_started_at, object),\n\n -- Foreign key to parent sync run\n CONSTRAINT fk_sync_obj_run_parent\n FOREIGN KEY ("_account_id", run_started_at) REFERENCES "stripe"."_sync_run" ("_account_id", started_at)\n);\n\n-- Step 4: Add updated_at trigger for _sync_obj_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_obj_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 5: Create indexes for efficient queries\nCREATE INDEX IF NOT EXISTS idx_sync_run_account_status\n ON "stripe"."_sync_run" ("_account_id", status);\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_run_status\n ON "stripe"."_sync_obj_run" ("_account_id", run_started_at, status);\n\n-- Step 6: Create sync_dashboard view for observability\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n r."_account_id" as account_id,\n r.started_at as run_started_at,\n r.status as run_status,\n r.completed_at as run_completed_at,\n r.max_concurrent,\n r.triggered_by,\n o.object,\n o.status as object_status,\n o.started_at as object_started_at,\n o.completed_at as object_completed_at,\n o.processed_count,\n o.error_message,\n o.updated_at,\n -- Duration in seconds\n EXTRACT(EPOCH FROM (COALESCE(o.completed_at, now()) - o.started_at))::integer as duration_seconds,\n -- Stale detection: running but no update in 5 min\n CASE\n WHEN o.status = \'running\' AND o.updated_at < now() - interval \'5 minutes\'\n THEN true\n ELSE false\n END as is_stale\nFROM "stripe"."_sync_run" r\nLEFT JOIN "stripe"."_sync_obj_run" o\n ON o."_account_id" = r."_account_id"\n AND o.run_started_at = r.started_at;\n', - }, - { - name: '0054_drop_sync_status.sql', - sql: '-- Drop the old _sync_status table\n-- This table has been replaced by _sync_run and _sync_obj_run for better observability\n-- See migration 0053_sync_observability.sql\n\nDROP TABLE IF EXISTS "stripe"."_sync_status";\n', - }, - { - name: '0055_bigint_money_columns.sql', - sql: '-- Fix generated columns: must drop and recreate with ::bigint cast\n-- Money columns that can overflow PostgreSQL integer max (~2.1 billion)\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::bigint) STORED;\n\n-- customers\nALTER TABLE "stripe"."customers" DROP COLUMN "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'balance\')::bigint) STORED;\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP COLUMN "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'tax\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::bigint) STORED;\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\n\n-- prices\nALTER TABLE "stripe"."prices" DROP COLUMN "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::bigint) STORED;\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP COLUMN "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\n', - }, - { - name: '0056_sync_run_closed_at.sql', - sql: '-- Add closed_at column to _sync_run\n-- closed_at IS NULL means the run is still active\n-- Status is derived from object states when closed_at IS NOT NULL\n\n-- Step 1: Drop dependent view first\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Drop the old constraint, status column, and completed_at column\nALTER TABLE "stripe"."_sync_run" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS status;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS completed_at;\n\n-- Step 3: Add closed_at column\nALTER TABLE "stripe"."_sync_run" ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;\n\n-- Step 4: Create exclusion constraint (only one active run per account)\nALTER TABLE "stripe"."_sync_run"\nADD CONSTRAINT one_active_run_per_account\nEXCLUDE ("_account_id" WITH =) WHERE (closed_at IS NULL);\n\n-- Step 5: Recreate sync_dashboard view (run-level only, one row per run)\n-- Base table: _sync_run (parent sync operation)\n-- Child table: _sync_obj_run (individual object syncs)\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n run."_account_id" as account_id,\n run.started_at,\n run.closed_at,\n run.max_concurrent,\n run.triggered_by,\n run.updated_at,\n -- Derived status from object states\n CASE\n WHEN run.closed_at IS NULL THEN \'running\'\n WHEN EXISTS (\n SELECT 1 FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ) THEN \'error\'\n ELSE \'complete\'\n END as status,\n -- First error message from failed objects\n (SELECT obj.error_message FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ORDER BY obj.object LIMIT 1) as error_message,\n -- Total processed count across all objects\n COALESCE((SELECT SUM(obj.processed_count) FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at), 0) as processed_count\nFROM "stripe"."_sync_run" run;\n', - }, - { - name: '0057_rename_sync_tables.sql', - sql: '-- Rename sync observability tables and create public sync_runs view\n-- Internal tables use _ prefix, public view is sync_runs\n\n-- Step 1: Drop the old sync_dashboard view\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Rename tables to plural (keep _ prefix for internal tables)\nALTER TABLE "stripe"."_sync_run" RENAME TO "_sync_runs";\nALTER TABLE "stripe"."_sync_obj_run" RENAME TO "_sync_obj_runs";\n\n-- Step 3: Update foreign key constraint name\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS fk_sync_obj_run_parent;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT fk_sync_obj_runs_parent\n FOREIGN KEY ("_account_id", run_started_at)\n REFERENCES "stripe"."_sync_runs" ("_account_id", started_at);\n\n-- Step 4: Recreate indexes with new table names\nDROP INDEX IF EXISTS "stripe"."idx_sync_run_account_status";\nDROP INDEX IF EXISTS "stripe"."idx_sync_obj_run_status";\n\nCREATE INDEX idx_sync_runs_account_status\n ON "stripe"."_sync_runs" ("_account_id", closed_at);\n\nCREATE INDEX idx_sync_obj_runs_status\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status);\n\n-- Step 5: Create public sync_runs view (one row per run with aggregates)\nCREATE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n', - }, - { - name: '0058_improve_sync_runs_status.sql', - sql: "-- Improve sync_runs view status logic\n-- More granular status based on actual object run states\n\nDROP VIEW IF EXISTS \"stripe\".\"sync_runs\";\n\nCREATE VIEW \"stripe\".\"sync_runs\" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count,\n COUNT(*) FILTER (WHERE o.status = 'error') as error_count,\n COUNT(*) FILTER (WHERE o.status = 'running') as running_count,\n COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n -- Run still open (closed_at IS NULL)\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending'\n WHEN r.closed_at IS NULL THEN 'running'\n -- Run closed (closed_at IS NOT NULL)\n WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error'\n ELSE 'complete'\n END as status\nFROM \"stripe\".\"_sync_runs\" r\nLEFT JOIN \"stripe\".\"_sync_obj_runs\" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n", - }, - { - name: '0059_sigma_subscription_item_change_events_v2_beta.sql', - sql: '-- event_timestamp and event_type are not generated columns because they are not immutable. \n-- Postgres requires generated expressions to be immutable.\n\nCREATE TABLE IF NOT EXISTS "stripe"."subscription_item_change_events_v2_beta" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "event_timestamp" timestamptz NOT NULL,\n "event_type" text NOT NULL,\n "subscription_item_id" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "event_timestamp", "event_type", "subscription_item_id")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n DROP CONSTRAINT IF EXISTS fk_subscription_item_change_events_v2_beta_account;\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD CONSTRAINT fk_subscription_item_change_events_v2_beta_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."subscription_item_change_events_v2_beta";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."subscription_item_change_events_v2_beta"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "currency" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'currency\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "mrr_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'mrr_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "quantity_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'quantity_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "subscription_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'subscription_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "customer_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'customer_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "price_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'price_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "product_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'product_id\', \'\'))::text) STORED;\n\n-- Keep as text to avoid non-immutable timestamp casts in a generated column\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "local_event_timestamp" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'local_event_timestamp\', \'\'))::text) STORED;', - }, - { - name: '0060_sigma_exchange_rates_from_usd.sql', - sql: '\nCREATE TABLE IF NOT EXISTS "stripe"."exchange_rates_from_usd" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "date" date NOT NULL,\n "sell_currency" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "date", "sell_currency")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."exchange_rates_from_usd"\n DROP CONSTRAINT IF EXISTS fk_exchange_rates_from_usd_account;\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD CONSTRAINT fk_exchange_rates_from_usd_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."exchange_rates_from_usd";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."exchange_rates_from_usd"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD COLUMN IF NOT EXISTS "buy_currency_exchange_rates" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'buy_currency_exchange_rates\', \'\'))::text) STORED;\n\n-- Index on date for efficient range queries\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_date\n ON "stripe"."exchange_rates_from_usd" ("date");\n\n-- Index on sell_currency for filtering by currency\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_sell_currency\n ON "stripe"."exchange_rates_from_usd" ("sell_currency");\n\n', - }, - { - name: '0061_add_page_cursor.sql', - sql: '-- Add page_cursor column for pagination state within a single sync run.\n-- This is used to store the starting_after ID for backfills using Stripe list calls.\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS page_cursor text;\n', - }, - { - name: '0062_sigma_query_runs.sql', - sql: '-- Allow parallel sync runs per triggered_by (sigma-worker vs stripe-worker)\nALTER TABLE "stripe"."_sync_runs" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\nADD CONSTRAINT one_active_run_per_account_triggered_by\nEXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n) WHERE (closed_at IS NULL);\n', - }, - { - name: '0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql', - sql: '-- Drop unused sigma beta tables if they exist.\nDROP TABLE IF EXISTS "stripe"."subscription_item_change_events_v2_beta";\nDROP TABLE IF EXISTS "stripe"."exchange_rates_from_usd";\n', - }, - { - name: '0064_add_created_gte_lte.sql', - sql: '-- Add created_gte / created_lte columns for time-range partitioned parallel sync.\n-- Workers use these to scope their Stripe list calls to a specific created window.\n-- Stored as Unix epoch seconds (INTEGER) to match Stripe\'s created filter format.\n-- created_gte defaults to 0 for non-chunked rows (required by PK).\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_gte INTEGER NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_lte INTEGER;\n\n-- Expand PK to include created_gte so multiple time-range chunks of the same object can coexist.\n-- PK constraint kept original name from 0053 (_sync_obj_run_pkey) after table rename in 0057.\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_run_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte);\n', - }, - { - name: '0065_add_created_lte_to_pk.sql', - sql: '-- Include created_lte in the PK so chunks with the same created_gte but\n-- different created_lte can coexist. Requires a NOT NULL default first.\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET DEFAULT 0;\n\nUPDATE "stripe"."_sync_obj_runs"\n SET created_lte = 0\n WHERE created_lte IS NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET NOT NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte, created_lte);\n', - }, - { - name: '0066_rate_limits.sql', - sql: '-- Rate limiting table and function for cross-process request throttling.\n-- Used by claimNextTask to cap how many claims/sec hit the database.\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n', - }, - { - name: '0067_add_priority_to_sync_obj_runs.sql', - sql: '-- Add priority column to _sync_obj_runs for deterministic task ordering.\n-- Priority mirrors the `order` field from resourceRegistry so workers\n-- always process parent resources (products, prices) before children\n-- (subscriptions, invoices).\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_runs_priority\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status, priority);\n', - }, - { - name: '0068_sync_obj_progress_view.sql', - sql: '-- Per-object sync progress view for monitoring.\n-- Defaults to the newest run per account; callers can filter by a specific\n-- run_started_at if needed.\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\n\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', - }, -] diff --git a/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql b/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql new file mode 100644 index 00000000..90015f35 --- /dev/null +++ b/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql @@ -0,0 +1,237 @@ +-- Internal sync metadata schema bootstrap for OpenAPI runtime. +-- Uses idempotent DDL so it can be safely re-run. + +CREATE EXTENSION IF NOT EXISTS btree_gist; + +CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW._updated_at = now(); + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + +CREATE TABLE IF NOT EXISTS "stripe"."accounts" ( + "_raw_data" jsonb NOT NULL, + "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, + "api_key_hashes" text[] NOT NULL DEFAULT '{}', + "first_synced_at" timestamptz NOT NULL DEFAULT now(), + "_last_synced_at" timestamptz NOT NULL DEFAULT now(), + "_updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +CREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes" + ON "stripe"."accounts" USING GIN ("api_key_hashes"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."accounts" +FOR EACH ROW EXECUTE FUNCTION set_updated_at(); + +CREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" ( + "id" text PRIMARY KEY, + "object" text, + "url" text NOT NULL, + "enabled_events" jsonb NOT NULL, + "description" text, + "enabled" boolean, + "livemode" boolean, + "metadata" jsonb, + "secret" text NOT NULL, + "status" text, + "api_version" text, + "created" bigint, + "last_synced_at" timestamptz, + "updated_at" timestamptz NOT NULL DEFAULT now(), + "account_id" text NOT NULL +); +ALTER TABLE "stripe"."_managed_webhooks" + DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique"; +ALTER TABLE "stripe"."_managed_webhooks" + ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id"); +ALTER TABLE "stripe"."_managed_webhooks" + DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account"; +ALTER TABLE "stripe"."_managed_webhooks" + ADD CONSTRAINT "fk_managed_webhooks_account" + FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id); +CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status" + ON "stripe"."_managed_webhooks" ("status"); +CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled" + ON "stripe"."_managed_webhooks" ("enabled"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_managed_webhooks" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); + +CREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" ( + "_account_id" text NOT NULL, + "started_at" timestamptz NOT NULL DEFAULT now(), + "closed_at" timestamptz, + "max_concurrent" integer NOT NULL DEFAULT 3, + "triggered_by" text, + "error_message" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "started_at") +); +ALTER TABLE "stripe"."_sync_runs" + ADD COLUMN IF NOT EXISTS "error_message" text; +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS "fk_sync_runs_account"; +ALTER TABLE "stripe"."_sync_runs" + ADD CONSTRAINT "fk_sync_runs_account" + FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS one_active_run_per_account; +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by; +ALTER TABLE "stripe"."_sync_runs" + ADD CONSTRAINT one_active_run_per_account_triggered_by + EXCLUDE ( + "_account_id" WITH =, + COALESCE(triggered_by, 'default') WITH = + ) WHERE (closed_at IS NULL); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_sync_runs" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); +CREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status" + ON "stripe"."_sync_runs" ("_account_id", "closed_at"); + +CREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" ( + "_account_id" text NOT NULL, + "run_started_at" timestamptz NOT NULL, + "object" text NOT NULL, + "status" text NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'running', 'complete', 'error')), + "started_at" timestamptz, + "completed_at" timestamptz, + "processed_count" integer NOT NULL DEFAULT 0, + "cursor" text, + "page_cursor" text, + "created_gte" integer NOT NULL DEFAULT 0, + "created_lte" integer NOT NULL DEFAULT 0, + "priority" integer NOT NULL DEFAULT 0, + "error_message" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte") +); +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "page_cursor" text; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "error_message" text; +ALTER TABLE "stripe"."_sync_obj_runs" + DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent"; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD CONSTRAINT "fk_sync_obj_runs_parent" + FOREIGN KEY ("_account_id", "run_started_at") + REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_sync_obj_runs" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); +CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); +CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority"); + +CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( + key TEXT PRIMARY KEY, + count INTEGER NOT NULL DEFAULT 0, + window_start TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( + rate_key TEXT, + max_requests INTEGER, + window_seconds INTEGER +) +RETURNS VOID AS $$ +DECLARE + now TIMESTAMPTZ := clock_timestamp(); + window_length INTERVAL := make_interval(secs => window_seconds); + current_count INTEGER; +BEGIN + PERFORM pg_advisory_xact_lock(hashtext(rate_key)); + + INSERT INTO "stripe"."_rate_limits" (key, count, window_start) + VALUES (rate_key, 1, now) + ON CONFLICT (key) DO UPDATE + SET count = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN 1 + ELSE "_rate_limits".count + 1 + END, + window_start = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN now + ELSE "_rate_limits".window_start + END; + + SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; + + IF current_count > max_requests THEN + RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE VIEW "stripe"."sync_runs" AS +SELECT + r._account_id as account_id, + r.started_at, + r.closed_at, + r.triggered_by, + r.max_concurrent, + COALESCE(SUM(o.processed_count), 0) as total_processed, + COUNT(o.*) as total_objects, + COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, + COUNT(*) FILTER (WHERE o.status = 'error') as error_count, + COUNT(*) FILTER (WHERE o.status = 'running') as running_count, + COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, + STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, + CASE + WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' + WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' + WHEN r.closed_at IS NULL THEN 'running' + WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' + ELSE 'complete' + END as status +FROM "stripe"."_sync_runs" r +LEFT JOIN "stripe"."_sync_obj_runs" o + ON o._account_id = r._account_id + AND o.run_started_at = r.started_at +GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; + +DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ); +CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS +SELECT + r."_account_id" AS account_id, + r.run_started_at, + r.object, + ROUND( + 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), + 1 + ) AS pct_complete, + COALESCE(SUM(r.processed_count), 0) AS processed +FROM "stripe"."_sync_obj_runs" r +WHERE r.run_started_at = ( + SELECT MAX(s.started_at) + FROM "stripe"."_sync_runs" s + WHERE s."_account_id" = r."_account_id" +) +GROUP BY r."_account_id", r.run_started_at, r.object; diff --git a/packages/sync-engine/src/database/postgres.ts b/packages/sync-engine/src/database/postgres.ts index a735dd9f..caea87c4 100644 --- a/packages/sync-engine/src/database/postgres.ts +++ b/packages/sync-engine/src/database/postgres.ts @@ -4,10 +4,15 @@ import { QueryUtils, type InsertColumn } from './QueryUtils' type PostgresConfig = { schema: string + /** Schema for metadata tables (accounts, _sync_runs, etc.). Defaults to schema when not provided. */ + syncSchema?: string poolConfig: PoolConfig } const DAY = 60 * 60 * 24 + +//todo: move them into migrations. also think about what should be done with ORDERED_STRIPE_TABLES. +const METADATA_TABLES = new Set(['accounts', '_managed_webhooks', '_sync_runs', '_sync_obj_runs']) /** * All Stripe tables that store account-related data. * Ordered for safe cascade deletion: dependencies first, then parent tables last. @@ -67,9 +72,18 @@ export class PostgresClient { this.pool = new pg.Pool(config.poolConfig) } + private get syncSchema(): string { + return this.config.syncSchema ?? this.config.schema + } + + private schemaForTable(table: string): string { + return METADATA_TABLES.has(table) ? this.syncSchema : this.config.schema + } + async delete(table: string, id: string): Promise { + const schema = this.schemaForTable(table) const prepared = sql(` - delete from "${this.config.schema}"."${table}" + delete from "${schema}"."${table}" where id = :id returning id; `)({ id }) @@ -137,7 +151,7 @@ export class PostgresClient { if (!entries.length) return [] const targetSchema = table.startsWith('_') - ? this.config.schema + ? this.syncSchema : (schemaOverride ?? this.config.schema) // Max 5 in parallel to avoid exhausting connection pool @@ -249,8 +263,9 @@ export class PostgresClient { async findMissingEntries(table: string, ids: string[]): Promise { if (!ids.length) return [] + const schema = this.schemaForTable(table) const prepared = sql(` - select id from "${this.config.schema}"."${table}" + select id from "${schema}"."${table}" where id=any(:ids::text[]); `)({ ids }) @@ -276,7 +291,7 @@ export class PostgresClient { // Upsert account and add API key hash to array if not already present // Note: id is auto-generated from _raw_data->>'id' await this.query( - `INSERT INTO "${this.config.schema}"."accounts" ("_raw_data", "api_key_hashes", "first_synced_at", "_last_synced_at") + `INSERT INTO "${this.syncSchema}"."accounts" ("_raw_data", "api_key_hashes", "first_synced_at", "_last_synced_at") VALUES ($1::jsonb, ARRAY[$2], now(), now()) ON CONFLICT (id) DO UPDATE SET @@ -284,7 +299,7 @@ export class PostgresClient { "api_key_hashes" = ( SELECT ARRAY( SELECT DISTINCT unnest( - COALESCE("${this.config.schema}"."accounts"."api_key_hashes", '{}') || ARRAY[$2] + COALESCE("${this.syncSchema}"."accounts"."api_key_hashes", '{}') || ARRAY[$2] ) ) ), @@ -297,7 +312,7 @@ export class PostgresClient { // eslint-disable-next-line @typescript-eslint/no-explicit-any async getAllAccounts(): Promise { const result = await this.query( - `SELECT _raw_data FROM "${this.config.schema}"."accounts" + `SELECT _raw_data FROM "${this.syncSchema}"."accounts" ORDER BY _last_synced_at DESC` ) return result.rows.map((row) => row._raw_data) @@ -324,7 +339,7 @@ export class PostgresClient { */ async getAccountIdByApiKeyHash(apiKeyHash: string): Promise { const result = await this.query( - `SELECT id FROM "${this.config.schema}"."accounts" + `SELECT id FROM "${this.syncSchema}"."accounts" WHERE $1 = ANY(api_key_hashes) LIMIT 1`, [apiKeyHash] @@ -340,7 +355,7 @@ export class PostgresClient { // eslint-disable-next-line @typescript-eslint/no-explicit-any async getAccountByApiKeyHash(apiKeyHash: string): Promise { const result = await this.query( - `SELECT _raw_data FROM "${this.config.schema}"."accounts" + `SELECT _raw_data FROM "${this.syncSchema}"."accounts" WHERE $1 = ANY(api_key_hashes) LIMIT 1`, [apiKeyHash] @@ -357,8 +372,9 @@ export class PostgresClient { for (const table of ORDERED_STRIPE_TABLES) { const accountIdColumn = this.getAccountIdColumn(table) + const schema = this.schemaForTable(table) const result = await this.query( - `SELECT COUNT(*) as count FROM "${this.config.schema}"."${table}" + `SELECT COUNT(*) as count FROM "${schema}"."${table}" WHERE "${accountIdColumn}" = $1`, [accountId] ) @@ -403,7 +419,7 @@ export class PostgresClient { currentActiveEntitlementIds: string[] ): Promise<{ rowCount: number }> { const prepared = sql(` - delete from "stripe"."active_entitlements" + delete from "${this.config.schema}"."active_entitlements" where customer = :customerId and id <> ALL(:currentActiveEntitlementIds::text[]); `)({ customerId, currentActiveEntitlementIds }) const { rowCount } = await this.query(prepared.text, prepared.values) @@ -500,8 +516,9 @@ export class PostgresClient { // Delete from all dependent tables for (const table of ORDERED_STRIPE_TABLES) { const accountIdColumn = this.getAccountIdColumn(table) + const schema = this.schemaForTable(table) const result = await this.query( - `DELETE FROM "${this.config.schema}"."${table}" + `DELETE FROM "${schema}"."${table}" WHERE "${accountIdColumn}" = $1`, [accountId] ) @@ -510,7 +527,7 @@ export class PostgresClient { // Finally, delete the account itself const accountResult = await this.query( - `DELETE FROM "${this.config.schema}"."accounts" + `DELETE FROM "${this.syncSchema}"."accounts" WHERE "id" = $1`, [accountId] ) @@ -616,7 +633,7 @@ export class PostgresClient { async cancelStaleRuns(accountId: string): Promise { // Step 1: Mark all running objects in stale runs as failed await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" o + `UPDATE "${this.syncSchema}"."_sync_obj_runs" o SET status = 'error', error_message = 'Auto-cancelled: stale (no update in 5 min)', completed_at = now(), @@ -629,17 +646,17 @@ export class PostgresClient { // Step 2: Close runs where all objects are in terminal state (complete or error) await this.query( - `UPDATE "${this.config.schema}"."_sync_runs" r + `UPDATE "${this.syncSchema}"."_sync_runs" r SET closed_at = now() WHERE r."_account_id" = $1 AND r.closed_at IS NULL AND EXISTS ( - SELECT 1 FROM "${this.config.schema}"."_sync_obj_runs" o + SELECT 1 FROM "${this.syncSchema}"."_sync_obj_runs" o WHERE o."_account_id" = r."_account_id" AND o.run_started_at = r.started_at ) AND NOT EXISTS ( - SELECT 1 FROM "${this.config.schema}"."_sync_obj_runs" o + SELECT 1 FROM "${this.syncSchema}"."_sync_obj_runs" o WHERE o."_account_id" = r."_account_id" AND o.run_started_at = r.started_at AND o.status IN ('pending', 'running') @@ -670,12 +687,12 @@ export class PostgresClient { const findExisting = async () => { const existing = triggeredByValue ? await this.query( - `SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs" + `SELECT "_account_id", started_at FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1 AND closed_at IS NULL AND triggered_by = $2`, [accountId, triggeredByValue] ) : await this.query( - `SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs" + `SELECT "_account_id", started_at FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1 AND closed_at IS NULL AND triggered_by IS NULL`, [accountId] ) @@ -697,7 +714,7 @@ export class PostgresClient { // Use date_trunc to ensure millisecond precision for JavaScript Date compatibility try { const result = await this.query( - `INSERT INTO "${this.config.schema}"."_sync_runs" ("_account_id", triggered_by, started_at) + `INSERT INTO "${this.syncSchema}"."_sync_runs" ("_account_id", triggered_by, started_at) VALUES ($1, $2, date_trunc('milliseconds', now())) RETURNING "_account_id", started_at`, [accountId, triggeredByValue] @@ -751,12 +768,12 @@ export class PostgresClient { ): Promise<{ accountId: string; runStartedAt: Date } | null> { const result = await this.query( `SELECT r."_account_id", r.started_at - FROM "${this.config.schema}"."_sync_runs" r + FROM "${this.syncSchema}"."_sync_runs" r WHERE r."_account_id" = $1 AND r.closed_at IS NOT NULL AND r.closed_at >= now() - make_interval(secs => $2) AND NOT EXISTS ( - SELECT 1 FROM "${this.config.schema}"."_sync_obj_runs" o + SELECT 1 FROM "${this.syncSchema}"."_sync_obj_runs" o WHERE o."_account_id" = r."_account_id" AND o.run_started_at = r.started_at AND o.status = 'error' @@ -798,12 +815,12 @@ export class PostgresClient { ): Promise<{ accountId: string; runStartedAt: Date } | null> { const result = triggeredBy ? await this.query( - `SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs" + `SELECT "_account_id", started_at FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1 AND closed_at IS NULL AND triggered_by = $2`, [accountId, triggeredBy] ) : await this.query( - `SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs" + `SELECT "_account_id", started_at FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1 AND closed_at IS NULL`, [accountId] ) @@ -828,7 +845,7 @@ export class PostgresClient { } | null> { const result = await this.query( `SELECT "_account_id", started_at, max_concurrent, closed_at - FROM "${this.config.schema}"."_sync_runs" + FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1 AND started_at = $2`, [accountId, runStartedAt] ) @@ -852,7 +869,7 @@ export class PostgresClient { maxConcurrent: number ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_runs" + `UPDATE "${this.syncSchema}"."_sync_runs" SET max_concurrent = GREATEST(max_concurrent, $3) WHERE "_account_id" = $1 AND started_at = $2`, [accountId, runStartedAt, maxConcurrent] @@ -865,7 +882,7 @@ export class PostgresClient { */ async closeSyncRun(accountId: string, runStartedAt: Date): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_runs" + `UPDATE "${this.syncSchema}"."_sync_runs" SET closed_at = now() WHERE "_account_id" = $1 AND started_at = $2 AND closed_at IS NULL`, [accountId, runStartedAt] @@ -898,7 +915,7 @@ export class PostgresClient { }) await this.query( - `INSERT INTO "${this.config.schema}"."_sync_obj_runs" ("_account_id", run_started_at, object, priority) + `INSERT INTO "${this.syncSchema}"."_sync_obj_runs" ("_account_id", run_started_at, object, priority) VALUES ${valueClauses.join(', ')} ON CONFLICT ("_account_id", run_started_at, object, created_gte, created_lte) DO NOTHING`, params @@ -930,7 +947,7 @@ export class PostgresClient { if (valueClauses.length === 0) return await this.query( - `INSERT INTO "${this.config.schema}"."_sync_obj_runs" + `INSERT INTO "${this.syncSchema}"."_sync_obj_runs" ("_account_id", run_started_at, object, created_gte, created_lte, priority) VALUES ${valueClauses.join(', ')} ON CONFLICT ("_account_id", run_started_at, object, created_gte, created_lte) DO NOTHING`, @@ -959,7 +976,7 @@ export class PostgresClient { // 2. Try to claim this object (atomic) const result = await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'running', started_at = now(), updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND status = 'pending' RETURNING *`, @@ -990,18 +1007,18 @@ export class PostgresClient { const run = await this.getSyncRun(accountId, runStartedAt) if (!run) return null - await this.query(`SELECT "${this.config.schema}".check_rate_limit($1, $2, $3)`, [ + await this.query(`SELECT "${this.syncSchema}".check_rate_limit($1, $2, $3)`, [ 'claimNextTask', rateLimit, 1, ]) const result = await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'running', started_at = now(), updated_at = now() WHERE ("_account_id", run_started_at, object, created_gte, created_lte) = ( SELECT "_account_id", run_started_at, object, created_gte, created_lte - FROM "${this.config.schema}"."_sync_obj_runs" + FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'pending' @@ -1040,7 +1057,7 @@ export class PostgresClient { } | null> { const result = await this.query( `SELECT object, status, processed_count, cursor, page_cursor - FROM "${this.config.schema}"."_sync_obj_runs" + FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`, [accountId, runStartedAt, object] ) @@ -1069,7 +1086,7 @@ export class PostgresClient { createdLte: number = 0 ): Promise { const result = await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET processed_count = processed_count + $4, updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $5 AND created_lte = $6 RETURNING processed_count`, @@ -1120,7 +1137,7 @@ export class PostgresClient { } const result = await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET ${sets.join(', ')} WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $4 AND created_lte = $5 @@ -1148,7 +1165,7 @@ export class PostgresClient { pageCursor: string | null ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET page_cursor = $4, updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`, [accountId, runStartedAt, object, pageCursor] @@ -1168,7 +1185,7 @@ export class PostgresClient { createdLte: number = 0 ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'pending', page_cursor = $4, updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $5 AND created_lte = $6`, [accountId, runStartedAt, object, pageCursor, createdGte, createdLte] @@ -1196,7 +1213,7 @@ export class PostgresClient { runStartedAt: Date ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET cursor = NULL, updated_at = now() WHERE "_account_id" = $1 AND object = $2 @@ -1214,7 +1231,7 @@ export class PostgresClient { createdLte: number = 0 ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET cursor = $4, updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $5 AND created_lte = $6`, [accountId, runStartedAt, object, cursor, createdGte, createdLte] @@ -1228,7 +1245,7 @@ export class PostgresClient { cursor: string | null ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET cursor = $4, updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`, [accountId, runStartedAt, object, cursor] @@ -1251,7 +1268,7 @@ export class PostgresClient { } const result = await this.query( - `SELECT object FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT object FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status = $3 ${filterClause} ORDER BY object`, @@ -1270,7 +1287,7 @@ export class PostgresClient { ): Promise> { const result = await this.query( `SELECT object, SUM(processed_count)::int AS processed_count - FROM "${this.config.schema}"."_sync_obj_runs" + FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 GROUP BY object`, [accountId, runStartedAt] @@ -1301,7 +1318,7 @@ export class PostgresClient { WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C") ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text END as cursor - FROM "${this.config.schema}"."_sync_obj_runs" o + FROM "${this.syncSchema}"."_sync_obj_runs" o WHERE o."_account_id" = $1 AND o.object = $2 AND o.cursor IS NOT NULL @@ -1324,7 +1341,7 @@ export class PostgresClient { WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C") ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text END as cursor - FROM "${this.config.schema}"."_sync_obj_runs" o + FROM "${this.syncSchema}"."_sync_obj_runs" o WHERE o."_account_id" = $1 AND o.object = $2 AND o.cursor IS NOT NULL @@ -1346,7 +1363,7 @@ export class PostgresClient { ): Promise { const result = await this.query( `SELECT cursor - FROM "${this.config.schema}"."_sync_obj_runs" + FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND object = $2 AND cursor IS NOT NULL @@ -1365,11 +1382,10 @@ export class PostgresClient { */ async deleteSyncRuns(accountId: string): Promise { // Delete object runs first (foreign key constraint) - await this.query( - `DELETE FROM "${this.config.schema}"."_sync_obj_runs" WHERE "_account_id" = $1`, - [accountId] - ) - await this.query(`DELETE FROM "${this.config.schema}"."_sync_runs" WHERE "_account_id" = $1`, [ + await this.query(`DELETE FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1`, [ + accountId, + ]) + await this.query(`DELETE FROM "${this.syncSchema}"."_sync_runs" WHERE "_account_id" = $1`, [ accountId, ]) } @@ -1385,7 +1401,7 @@ export class PostgresClient { runStartedAt: Date, stuckThresholdSeconds?: number ): Promise { - const baseQuery = `UPDATE "${this.config.schema}"."_sync_obj_runs" + const baseQuery = `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'pending', updated_at = now() WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'running'` @@ -1413,7 +1429,7 @@ export class PostgresClient { createdLte: number = 0 ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'complete', completed_at = now(), page_cursor = NULL WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $4 AND created_lte = $5`, [accountId, runStartedAt, object, createdGte, createdLte] @@ -1439,7 +1455,7 @@ export class PostgresClient { createdLte: number = 0 ): Promise { await this.query( - `UPDATE "${this.config.schema}"."_sync_obj_runs" + `UPDATE "${this.syncSchema}"."_sync_obj_runs" SET status = 'error', error_message = $6, completed_at = now(), page_cursor = NULL WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND created_gte = $4 AND created_lte = $5`, [accountId, runStartedAt, object, createdGte, createdLte, errorMessage] @@ -1457,7 +1473,7 @@ export class PostgresClient { */ async hasAnyObjectErrors(accountId: string, runStartedAt: Date): Promise { const result = await this.query( - `SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT COUNT(*) as count FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'error'`, [accountId, runStartedAt] ) @@ -1469,7 +1485,7 @@ export class PostgresClient { */ async countRunningObjects(accountId: string, runStartedAt: Date): Promise { const result = await this.query( - `SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT COUNT(*) as count FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'running'`, [accountId, runStartedAt] ) @@ -1489,7 +1505,7 @@ export class PostgresClient { if (runningCount >= run.maxConcurrent) return null const result = await this.query( - `SELECT object FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT object FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'pending' ORDER BY priority, object LIMIT 1`, @@ -1504,7 +1520,7 @@ export class PostgresClient { */ async areAllObjectsComplete(accountId: string, runStartedAt: Date): Promise { const result = await this.query( - `SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT COUNT(*) as count FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2 AND status IN ('pending', 'running')`, [accountId, runStartedAt] ) @@ -1513,7 +1529,7 @@ export class PostgresClient { async countObjectRuns(accountId: string, runStartedAt: Date): Promise { const result = await this.query( - `SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs" + `SELECT COUNT(*) as count FROM "${this.syncSchema}"."_sync_obj_runs" WHERE "_account_id" = $1 AND run_started_at = $2`, [accountId, runStartedAt] ) diff --git a/packages/sync-engine/src/index.ts b/packages/sync-engine/src/index.ts index 96fe5cba..362c427c 100644 --- a/packages/sync-engine/src/index.ts +++ b/packages/sync-engine/src/index.ts @@ -9,9 +9,7 @@ export { getTableName } from './resourceRegistry' export type * from './types' export { PostgresClient } from './database/postgres' -export { runMigrations, runMigrationsFromContent } from './database/migrate' -export { embeddedMigrations } from './database/migrations-embedded' -export type { EmbeddedMigration } from './database/migrations-embedded' +export { runMigrations } from './database/migrate' export { hashApiKey } from './utils/hashApiKey' export { createStripeWebSocketClient } from './websocket-client' export type { diff --git a/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts index 0261237e..a94a669c 100644 --- a/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts +++ b/packages/sync-engine/src/openapi/__tests__/postgresAdapter.test.ts @@ -14,12 +14,19 @@ const SAMPLE_TABLE: ParsedResourceTable = { ], } +const EXPANDABLE_REFERENCE_TABLE: ParsedResourceTable = { + tableName: 'charges', + resourceId: 'charge', + sourceSchemaName: 'charge', + columns: [{ name: 'customer', type: 'json', nullable: true, expandableReference: true }], +} + describe('PostgresAdapter', () => { it('emits deterministic DDL statements with runtime-required metadata columns', () => { const adapter = new PostgresAdapter({ schemaName: 'stripe' }) const statements = adapter.buildAllStatements([SAMPLE_TABLE]) - expect(statements).toHaveLength(5) + expect(statements).toHaveLength(9) expect(statements[0]).toContain('CREATE TABLE "stripe"."customers"') expect(statements[0]).toContain('"_raw_data" jsonb NOT NULL') expect(statements[0]).toContain('"_account_id" text NOT NULL') @@ -33,11 +40,23 @@ describe('PostgresAdapter', () => { expect(statements[0]).toContain( '"expires_at" text GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::text) STORED' ) - expect(statements[1]).toContain( + expect( + statements.some((stmt) => stmt.includes('ADD COLUMN IF NOT EXISTS "created" bigint')) + ).toBe(true) + expect( + statements.some((stmt) => stmt.includes('ADD COLUMN IF NOT EXISTS "deleted" boolean')) + ).toBe(true) + expect( + statements.some((stmt) => stmt.includes('ADD COLUMN IF NOT EXISTS "metadata" jsonb')) + ).toBe(true) + expect( + statements.some((stmt) => stmt.includes('ADD COLUMN IF NOT EXISTS "expires_at" text')) + ).toBe(true) + expect(statements[5]).toContain( 'FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)' ) - expect(statements[3]).toContain('DROP TRIGGER IF EXISTS handle_updated_at') - expect(statements[4]).toContain('EXECUTE FUNCTION set_updated_at()') + expect(statements[7]).toContain('DROP TRIGGER IF EXISTS handle_updated_at') + expect(statements[8]).toContain('EXECUTE FUNCTION set_updated_at()') }) it('produces stable output across repeated calls', () => { @@ -46,4 +65,25 @@ describe('PostgresAdapter', () => { const second = adapter.buildAllStatements([SAMPLE_TABLE]) expect(second).toEqual(first) }) + + it('materializes expandable reference columns as text ids for compatibility', () => { + const adapter = new PostgresAdapter({ schemaName: 'stripe' }) + const statements = adapter.buildAllStatements([EXPANDABLE_REFERENCE_TABLE]) + + expect(statements[0]).toContain('"customer" text GENERATED ALWAYS AS (CASE') + expect(statements[0]).toContain("WHEN jsonb_typeof(_raw_data->'customer') = 'object'") + expect(statements[0]).toContain("THEN (_raw_data->'customer'->>'id')") + }) + + it('uses accountSchema for FK when provided (split schema)', () => { + const adapter = new PostgresAdapter({ + schemaName: 'stripe_data', + accountSchema: 'stripe_sync', + }) + const statements = adapter.buildAllStatements([SAMPLE_TABLE]) + expect(statements[0]).toContain('CREATE TABLE "stripe_data"."customers"') + expect(statements[5]).toContain( + 'FOREIGN KEY ("_account_id") REFERENCES "stripe_sync"."accounts" (id)' + ) + }) }) diff --git a/packages/sync-engine/src/openapi/__tests__/specParser.test.ts b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts index 151b69ad..a44983a3 100644 --- a/packages/sync-engine/src/openapi/__tests__/specParser.test.ts +++ b/packages/sync-engine/src/openapi/__tests__/specParser.test.ts @@ -85,4 +85,46 @@ describe('SpecParser', () => { expect(reversed).toEqual(normal) }) + + it('marks expandable references from x-expansionResources metadata', () => { + const parser = new SpecParser() + const parsed = parser.parse( + { + ...minimalStripeOpenApiSpec, + components: { + schemas: { + charge: { + 'x-resourceId': 'charge', + type: 'object', + properties: { + id: { type: 'string' }, + customer: { + anyOf: [{ type: 'string' }, { $ref: '#/components/schemas/customer' }], + 'x-expansionResources': { + oneOf: [{ $ref: '#/components/schemas/customer' }], + }, + }, + }, + }, + customer: { + 'x-resourceId': 'customer', + type: 'object', + properties: { + id: { type: 'string' }, + }, + }, + }, + }, + }, + { allowedTables: ['charges'] } + ) + + const charges = parsed.tables.find((table) => table.tableName === 'charges') + expect(charges?.columns).toContainEqual({ + name: 'customer', + type: 'json', + nullable: false, + expandableReference: true, + }) + }) }) diff --git a/packages/sync-engine/src/openapi/index.ts b/packages/sync-engine/src/openapi/index.ts index bb17b190..a7f0b524 100644 --- a/packages/sync-engine/src/openapi/index.ts +++ b/packages/sync-engine/src/openapi/index.ts @@ -1,5 +1,11 @@ export type * from './types' -export { SpecParser, RUNTIME_REQUIRED_TABLES, RUNTIME_RESOURCE_ALIASES } from './specParser' +export { + SpecParser, + RUNTIME_REQUIRED_TABLES, + OPENAPI_RESOURCE_TABLE_ALIASES, + RUNTIME_RESOURCE_ALIASES, +} from './specParser' +export { OPENAPI_COMPATIBILITY_COLUMNS } from './runtimeMappings' export { PostgresAdapter } from './postgresAdapter' export { WritePathPlanner } from './writePathPlanner' export { resolveOpenApiSpec } from './specFetchHelper' diff --git a/packages/sync-engine/src/openapi/postgresAdapter.ts b/packages/sync-engine/src/openapi/postgresAdapter.ts index 87fb0b49..503d2ffe 100644 --- a/packages/sync-engine/src/openapi/postgresAdapter.ts +++ b/packages/sync-engine/src/openapi/postgresAdapter.ts @@ -6,15 +6,19 @@ const PG_IDENTIFIER_MAX_BYTES = 63 type PostgresAdapterOptions = { schemaName?: string + /** Schema for accounts table (FK target). Defaults to schemaName when not provided. */ + accountSchema?: string materializeTemporalAsText?: boolean } export class PostgresAdapter implements DialectAdapter { private readonly schemaName: string + private readonly accountSchema: string private readonly materializeTemporalAsText: boolean constructor(options: PostgresAdapterOptions = {}) { this.schemaName = options.schemaName ?? 'stripe' + this.accountSchema = options.accountSchema ?? this.schemaName this.materializeTemporalAsText = options.materializeTemporalAsText ?? true } @@ -28,6 +32,10 @@ export class PostgresAdapter implements DialectAdapter { const quotedSchema = this.quoteIdent(this.schemaName) const quotedTable = this.quoteIdent(table.tableName) const generatedColumns = table.columns.map((column) => this.buildGeneratedColumn(column)) + const generatedColumnAlters = generatedColumns.map( + (columnDef) => + `ALTER TABLE ${quotedSchema}.${quotedTable} ADD COLUMN IF NOT EXISTS ${columnDef};` + ) const columnDefs = [ '"_raw_data" jsonb NOT NULL', '"_last_synced_at" timestamptz', @@ -40,12 +48,14 @@ export class PostgresAdapter implements DialectAdapter { const fkName = this.safeIdentifier(`fk_${table.tableName}_account`) const accountIdxName = this.safeIdentifier(`idx_${table.tableName}_account_id`) + const quotedAccountSchema = this.quoteIdent(this.accountSchema) return [ `CREATE TABLE ${quotedSchema}.${quotedTable} (\n ${columnDefs.join(',\n ')}\n);`, + ...generatedColumnAlters, `ALTER TABLE ${quotedSchema}.${quotedTable} ADD CONSTRAINT ${this.quoteIdent( fkName - )} FOREIGN KEY ("_account_id") REFERENCES ${quotedSchema}."accounts" (id);`, + )} FOREIGN KEY ("_account_id") REFERENCES ${quotedAccountSchema}."accounts" (id);`, `CREATE INDEX ${this.quoteIdent(accountIdxName)} ON ${quotedSchema}.${quotedTable} ("_account_id");`, `DROP TRIGGER IF EXISTS handle_updated_at ON ${quotedSchema}.${quotedTable};`, `CREATE TRIGGER handle_updated_at BEFORE UPDATE ON ${quotedSchema}.${quotedTable} FOR EACH ROW EXECUTE FUNCTION set_updated_at();`, @@ -53,10 +63,12 @@ export class PostgresAdapter implements DialectAdapter { } private buildGeneratedColumn(column: ParsedColumn): string { - const pgType = this.pgType(column.type) + const forceReferenceText = column.expandableReference === true + const pgType = forceReferenceText ? 'text' : this.pgType(column.type) const escapedPath = column.name.replace(/'/g, "''") - const expression = - pgType === 'jsonb' + const expression = forceReferenceText + ? this.buildExpandableReferenceTextExpression(escapedPath) + : pgType === 'jsonb' ? `(_raw_data->'${escapedPath}')::jsonb` : pgType === 'text' ? `(_raw_data->>'${escapedPath}')::text` @@ -65,6 +77,15 @@ export class PostgresAdapter implements DialectAdapter { return `${this.quoteIdent(column.name)} ${pgType} GENERATED ALWAYS AS (${expression}) STORED` } + private buildExpandableReferenceTextExpression(escapedPath: string): string { + const jsonPath = `_raw_data->'${escapedPath}'` + return `CASE + WHEN jsonb_typeof(${jsonPath}) = 'object' AND ${jsonPath} ? 'id' + THEN (${jsonPath}->>'id') + ELSE (_raw_data->>'${escapedPath}') + END` + } + private pgType(type: ScalarType): string { if (type === 'timestamptz' && this.materializeTemporalAsText) { return 'text' diff --git a/packages/sync-engine/src/openapi/runtimeMappings.ts b/packages/sync-engine/src/openapi/runtimeMappings.ts new file mode 100644 index 00000000..622837da --- /dev/null +++ b/packages/sync-engine/src/openapi/runtimeMappings.ts @@ -0,0 +1,67 @@ +import { RESOURCE_TABLE_NAME_MAP, STRIPE_OBJECT_TO_SYNC_OBJECT_ALIASES } from '../resourceRegistry' +import type { ParsedColumn } from './types' + +const OPENAPI_ADDITIONAL_RESOURCE_TABLE_ALIASES: Record = { + // OpenAPI resource id differs from runtime sync object routing for this nested entitlement schema. + 'entitlements.feature': 'features', + // OpenAPI uses generic "item" for checkout session line items. + item: 'checkout_session_line_items', +} + +const OPENAPI_RESOURCE_TABLE_ALIASES_FROM_REGISTRY: Record = Object.fromEntries( + Object.entries(RESOURCE_TABLE_NAME_MAP).map(([objectName, tableName]) => [objectName, tableName]) +) + +const OPENAPI_RESOURCE_TABLE_ALIASES_FROM_STRIPE_OBJECT_ALIASES: Record = + Object.fromEntries( + Object.entries(STRIPE_OBJECT_TO_SYNC_OBJECT_ALIASES).map( + ([stripeObjectName, syncObjectName]) => [ + stripeObjectName, + RESOURCE_TABLE_NAME_MAP[syncObjectName], + ] + ) + ) + +/** + * OpenAPI resource-id to runtime table-name aliases. + * Most entries come directly from the runtime resource registry to avoid duplicating table config. + */ +export const OPENAPI_RESOURCE_TABLE_ALIASES: Record = { + ...OPENAPI_RESOURCE_TABLE_ALIASES_FROM_REGISTRY, + ...OPENAPI_RESOURCE_TABLE_ALIASES_FROM_STRIPE_OBJECT_ALIASES, + ...OPENAPI_ADDITIONAL_RESOURCE_TABLE_ALIASES, +} + +/** + * Compatibility columns that should exist even if not present in the current OpenAPI shape. + * This preserves backwards compatibility for existing queries and write paths. + * todo: Remove this + */ +export const OPENAPI_COMPATIBILITY_COLUMNS: Record = { + active_entitlements: [ + { name: 'customer', type: 'text', nullable: true }, + { name: 'object', type: 'text', nullable: true }, + { name: 'feature', type: 'text', nullable: true }, + { name: 'livemode', type: 'boolean', nullable: true }, + { name: 'lookup_key', type: 'text', nullable: true }, + ], + checkout_session_line_items: [ + { name: 'checkout_session', type: 'text', nullable: true }, + { name: 'amount_discount', type: 'bigint', nullable: true }, + { name: 'amount_tax', type: 'bigint', nullable: true }, + ], + customers: [{ name: 'deleted', type: 'boolean', nullable: true }], + early_fraud_warnings: [{ name: 'payment_intent', type: 'text', nullable: true }], + features: [ + { name: 'object', type: 'text', nullable: true }, + { name: 'name', type: 'text', nullable: true }, + { name: 'lookup_key', type: 'text', nullable: true }, + { name: 'active', type: 'boolean', nullable: true }, + { name: 'livemode', type: 'boolean', nullable: true }, + { name: 'metadata', type: 'json', nullable: true }, + ], + subscription_items: [ + { name: 'deleted', type: 'boolean', nullable: true }, + { name: 'subscription', type: 'text', nullable: true }, + ], +} diff --git a/packages/sync-engine/src/openapi/specParser.ts b/packages/sync-engine/src/openapi/specParser.ts index 23c0d6e7..a7f94c17 100644 --- a/packages/sync-engine/src/openapi/specParser.ts +++ b/packages/sync-engine/src/openapi/specParser.ts @@ -7,7 +7,11 @@ import type { ParsedOpenApiSpec, ScalarType, } from './types' -import { RUNTIME_REQUIRED_TABLES as DEFAULT_RUNTIME_REQUIRED_TABLES } from '../syncObjects' +import { RUNTIME_REQUIRED_TABLES as DEFAULT_RUNTIME_REQUIRED_TABLES } from '../resourceRegistry' +import { + OPENAPI_COMPATIBILITY_COLUMNS, + OPENAPI_RESOURCE_TABLE_ALIASES as DEFAULT_OPENAPI_RESOURCE_TABLE_ALIASES, +} from './runtimeMappings' const RESERVED_COLUMNS = new Set([ 'id', @@ -19,59 +23,14 @@ const RESERVED_COLUMNS = new Set([ export const RUNTIME_REQUIRED_TABLES = DEFAULT_RUNTIME_REQUIRED_TABLES -export const RUNTIME_RESOURCE_ALIASES: Record = { - active_entitlement: 'active_entitlements', - charge: 'charges', - 'checkout.session': 'checkout_sessions', - 'checkout.session.line_item': 'checkout_session_line_items', - 'checkout.session.line_items': 'checkout_session_line_items', - 'checkout.session_line_item': 'checkout_session_line_items', - credit_note: 'credit_notes', - customer: 'customers', - dispute: 'disputes', - 'entitlements.active_entitlement': 'active_entitlements', - 'entitlements.feature': 'features', - feature: 'features', - invoice: 'invoices', - item: 'checkout_session_line_items', - payment_intent: 'payment_intents', - payment_method: 'payment_methods', - plan: 'plans', - price: 'prices', - product: 'products', - 'radar.early_fraud_warning': 'early_fraud_warnings', - refund: 'refunds', - review: 'reviews', - setup_intent: 'setup_intents', - subscription: 'subscriptions', - subscription_item: 'subscription_items', - subscription_schedule: 'subscription_schedules', - tax_id: 'tax_ids', -} - -const COMPATIBILITY_COLUMNS: Record = { - active_entitlements: [ - { name: 'customer', type: 'text', nullable: true }, - { name: 'object', type: 'text', nullable: true }, - { name: 'feature', type: 'text', nullable: true }, - { name: 'livemode', type: 'boolean', nullable: true }, - { name: 'lookup_key', type: 'text', nullable: true }, - ], - checkout_session_line_items: [ - { name: 'checkout_session', type: 'text', nullable: true }, - { name: 'amount_discount', type: 'bigint', nullable: true }, - { name: 'amount_tax', type: 'bigint', nullable: true }, - ], - customers: [{ name: 'deleted', type: 'boolean', nullable: true }], - subscription_items: [ - { name: 'deleted', type: 'boolean', nullable: true }, - { name: 'subscription', type: 'text', nullable: true }, - ], -} +export const OPENAPI_RESOURCE_TABLE_ALIASES = DEFAULT_OPENAPI_RESOURCE_TABLE_ALIASES +/** @deprecated Use OPENAPI_RESOURCE_TABLE_ALIASES instead. */ +export const RUNTIME_RESOURCE_ALIASES = OPENAPI_RESOURCE_TABLE_ALIASES type ColumnAccumulator = { type: ScalarType nullable: boolean + expandableReference: boolean } export class SpecParser { @@ -81,7 +40,7 @@ export class SpecParser { throw new Error('OpenAPI spec is missing components.schemas') } - const aliases = { ...RUNTIME_RESOURCE_ALIASES, ...(options.resourceAliases ?? {}) } + const aliases = { ...OPENAPI_RESOURCE_TABLE_ALIASES, ...(options.resourceAliases ?? {}) } const allowedTables = new Set(options.allowedTables ?? RUNTIME_REQUIRED_TABLES) const tableMap = new Map< string, @@ -121,12 +80,17 @@ export class SpecParser { for (const column of parsedColumns) { const current = existing.columns.get(column.name) if (!current) { - existing.columns.set(column.name, { type: column.type, nullable: column.nullable }) + existing.columns.set(column.name, { + type: column.type, + nullable: column.nullable, + expandableReference: column.expandableReference ?? false, + }) continue } existing.columns.set(column.name, { type: this.mergeTypes(current.type, column.type), nullable: current.nullable || column.nullable, + expandableReference: current.expandableReference || (column.expandableReference ?? false), }) } @@ -141,12 +105,13 @@ export class SpecParser { sourceSchemaName: 'compatibility_fallback', columns: new Map(), } as const) - for (const compatibilityColumn of COMPATIBILITY_COLUMNS[tableName] ?? []) { + for (const compatibilityColumn of OPENAPI_COMPATIBILITY_COLUMNS[tableName] ?? []) { const existing = current.columns.get(compatibilityColumn.name) if (!existing) { current.columns.set(compatibilityColumn.name, { type: compatibilityColumn.type, nullable: compatibilityColumn.nullable, + expandableReference: compatibilityColumn.expandableReference ?? false, }) } } @@ -160,7 +125,12 @@ export class SpecParser { resourceId: table.resourceId, sourceSchemaName: table.sourceSchemaName, columns: Array.from(table.columns.entries()) - .map(([name, value]) => ({ name, type: value.type, nullable: value.nullable })) + .map(([name, value]) => ({ + name, + type: value.type, + nullable: value.nullable, + ...(value.expandableReference ? { expandableReference: true } : {}), + })) .sort((a, b) => a.name.localeCompare(b.name)), })) @@ -196,6 +166,7 @@ export class SpecParser { name: propertyName, type: inferred.type, nullable: inferred.nullable, + ...(inferred.expandableReference ? { expandableReference: true } : {}), }) } return columns @@ -204,20 +175,23 @@ export class SpecParser { private inferFromCandidates( candidates: OpenApiSchemaOrReference[], spec: OpenApiSpec - ): { type: ScalarType; nullable: boolean } { + ): { type: ScalarType; nullable: boolean; expandableReference: boolean } { if (candidates.length === 0) { - return { type: 'text', nullable: true } + return { type: 'text', nullable: true, expandableReference: false } } let mergedType: ScalarType | null = null let nullable = false + let expandableReference = false for (const candidate of candidates) { const inferred = this.inferType(candidate, spec) mergedType = mergedType ? this.mergeTypes(mergedType, inferred.type) : inferred.type nullable = nullable || inferred.nullable + expandableReference = + expandableReference || this.isExpandableReferenceCandidate(candidate, spec) } - return { type: mergedType ?? 'text', nullable } + return { type: mergedType ?? 'text', nullable, expandableReference } } private mergeTypes(left: ScalarType, right: ScalarType): ScalarType { @@ -280,6 +254,14 @@ export class SpecParser { return { type: 'text', nullable: true } } + private isExpandableReferenceCandidate( + schemaOrRef: OpenApiSchemaOrReference, + spec: OpenApiSpec + ): boolean { + const schema = this.resolveSchema(schemaOrRef, spec) + return Boolean(schema['x-expansionResources']) + } + private collectPropertyCandidates( schemaOrRef: OpenApiSchemaOrReference, spec: OpenApiSpec, diff --git a/packages/sync-engine/src/openapi/types.ts b/packages/sync-engine/src/openapi/types.ts index a9d4ca4c..dff2a898 100644 --- a/packages/sync-engine/src/openapi/types.ts +++ b/packages/sync-engine/src/openapi/types.ts @@ -10,6 +10,10 @@ export type OpenApiSchemaObject = { enum?: unknown[] additionalProperties?: boolean | OpenApiSchemaOrReference 'x-resourceId'?: string + 'x-expandableFields'?: string[] + 'x-expansionResources'?: { + oneOf?: OpenApiSchemaOrReference[] + } } export type OpenApiReferenceObject = { @@ -34,6 +38,7 @@ export type ParsedColumn = { name: string type: ScalarType nullable: boolean + expandableReference?: boolean } export type ParsedResourceTable = { diff --git a/packages/sync-engine/src/resourceRegistry.ts b/packages/sync-engine/src/resourceRegistry.ts index f5b8bd99..b430fcff 100644 --- a/packages/sync-engine/src/resourceRegistry.ts +++ b/packages/sync-engine/src/resourceRegistry.ts @@ -1,239 +1,297 @@ import Stripe from 'stripe' -import type { ResourceConfig } from './types' +import type { ResourceConfig, StripeListResourceConfig } from './types' import type { SigmaSyncProcessor } from './sigma/sigmaSyncProcessor' -export type StripeObject = - | 'product' - | 'price' - | 'plan' - | 'customer' - | 'subscription' - | 'subscription_schedules' - | 'invoice' - | 'charge' - | 'setup_intent' - | 'payment_method' - | 'payment_intent' - | 'tax_id' - | 'credit_note' - | 'dispute' - | 'early_fraud_warning' - | 'refund' - | 'checkout_sessions' - | 'active_entitlements' - | 'review' +interface ResourceDef { + readonly order: number + readonly tableName: string + readonly dependencies?: readonly string[] + readonly list: ( + s: Stripe + ) => ( + p: Stripe.PaginationParams & { created?: Stripe.RangeQueryParam } + ) => Promise<{ data: unknown[]; has_more: boolean }> + readonly retrieve: (s: Stripe) => (id: string) => Promise> + readonly supportsCreatedFilter: boolean + readonly sync: boolean + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly isFinalState?: (entity: any) => boolean + // Tables created from nested data (like line items) that don't have their own top-level list API. + readonly childTables?: readonly string[] + readonly listExpands?: readonly Record< + string, + (s: Stripe) => (id: string) => Promise> + >[] +} + +const RESOURCE_MAP: Record = { + product: { + order: 1, + tableName: 'products', + list: (s) => (p) => s.products.list(p), + retrieve: (s) => (id) => s.products.retrieve(id), + supportsCreatedFilter: true, + sync: true, + }, + price: { + order: 2, + tableName: 'prices', + dependencies: ['product'], + list: (s) => (p) => s.prices.list(p), + retrieve: (s) => (id) => s.prices.retrieve(id), + supportsCreatedFilter: true, + sync: true, + }, + plan: { + order: 3, + tableName: 'plans', + dependencies: ['product'], + list: (s) => (p) => s.plans.list(p), + retrieve: (s) => (id) => s.plans.retrieve(id), + supportsCreatedFilter: true, + sync: true, + }, + customer: { + order: 4, + tableName: 'customers', + list: (s) => (p) => s.customers.list(p), + retrieve: (s) => (id) => s.customers.retrieve(id), + supportsCreatedFilter: true, + sync: true, + isFinalState: (c: Stripe.Customer | Stripe.DeletedCustomer) => + 'deleted' in c && c.deleted === true, + }, + subscription: { + order: 5, + tableName: 'subscriptions', + dependencies: ['customer', 'price'], + list: (s) => (p) => s.subscriptions.list(p), + retrieve: (s) => (id) => s.subscriptions.retrieve(id), + listExpands: [ + { items: (s) => (id) => s.subscriptionItems.list({ subscription: id, limit: 100 }) }, + ], + supportsCreatedFilter: true, + sync: true, + childTables: ['subscription_items'], + isFinalState: (s: Stripe.Subscription) => + s.status === 'canceled' || s.status === 'incomplete_expired', + }, + subscription_schedules: { + order: 6, + tableName: 'subscription_schedules', + dependencies: ['customer'], + list: (s) => (p) => s.subscriptionSchedules.list(p), + retrieve: (s) => (id) => s.subscriptionSchedules.retrieve(id), + supportsCreatedFilter: true, + sync: true, + isFinalState: (s: Stripe.SubscriptionSchedule) => + s.status === 'canceled' || s.status === 'completed', + }, + invoice: { + order: 7, + tableName: 'invoices', + dependencies: ['customer', 'subscription'], + list: (s) => (p) => s.invoices.list(p), + retrieve: (s) => (id) => s.invoices.retrieve(id), + listExpands: [{ lines: (s) => (id) => s.invoices.listLineItems(id, { limit: 100 }) }], + supportsCreatedFilter: true, + sync: true, + isFinalState: (i: Stripe.Invoice) => i.status === 'void', + }, + charge: { + order: 8, + tableName: 'charges', + dependencies: ['customer', 'invoice'], + list: (s) => (p) => s.charges.list(p), + retrieve: (s) => (id) => s.charges.retrieve(id), + listExpands: [{ refunds: (s) => (id) => s.refunds.list({ charge: id, limit: 100 }) }], + supportsCreatedFilter: true, + sync: true, + isFinalState: (c: Stripe.Charge) => c.status === 'failed' || c.status === 'succeeded', + }, + setup_intent: { + order: 9, + tableName: 'setup_intents', + dependencies: ['customer'], + list: (s) => (p) => s.setupIntents.list(p), + retrieve: (s) => (id) => s.setupIntents.retrieve(id), + supportsCreatedFilter: true, + sync: true, + isFinalState: (s: Stripe.SetupIntent) => s.status === 'canceled' || s.status === 'succeeded', + }, + payment_method: { + order: 10, + tableName: 'payment_methods', + dependencies: ['customer'], + list: (s) => (p) => s.paymentMethods.list(p), + retrieve: (s) => (id) => s.paymentMethods.retrieve(id), + supportsCreatedFilter: false, + sync: true, + }, + payment_intent: { + order: 11, + tableName: 'payment_intents', + dependencies: ['customer', 'invoice'], + list: (s) => (p) => s.paymentIntents.list(p), + retrieve: (s) => (id) => s.paymentIntents.retrieve(id), + supportsCreatedFilter: true, + sync: true, + isFinalState: (p: Stripe.PaymentIntent) => p.status === 'canceled' || p.status === 'succeeded', + }, + tax_id: { + order: 12, + tableName: 'tax_ids', + dependencies: ['customer'], + list: (s) => (p) => s.taxIds.list(p), + retrieve: (s) => (id) => s.taxIds.retrieve(id), + supportsCreatedFilter: false, + sync: true, + }, + credit_note: { + order: 13, + tableName: 'credit_notes', + dependencies: ['customer', 'invoice'], + list: (s) => (p) => s.creditNotes.list(p), + retrieve: (s) => (id) => s.creditNotes.retrieve(id), + listExpands: [{ lines: (s) => (id) => s.creditNotes.listLineItems(id, { limit: 100 }) }], + supportsCreatedFilter: true, + sync: true, + isFinalState: (c: Stripe.CreditNote) => c.status === 'void', + }, + dispute: { + order: 14, + tableName: 'disputes', + dependencies: ['charge'], + list: (s) => (p) => s.disputes.list(p), + retrieve: (s) => (id) => s.disputes.retrieve(id), + supportsCreatedFilter: true, + sync: true, + isFinalState: (d: Stripe.Dispute) => d.status === 'won' || d.status === 'lost', + }, + early_fraud_warning: { + order: 15, + tableName: 'early_fraud_warnings', + dependencies: ['payment_intent', 'charge'], + list: (s) => (p) => s.radar.earlyFraudWarnings.list(p), + retrieve: (s) => (id) => s.radar.earlyFraudWarnings.retrieve(id), + supportsCreatedFilter: true, + sync: true, + }, + refund: { + order: 16, + tableName: 'refunds', + dependencies: ['payment_intent', 'charge'], + list: (s) => (p) => s.refunds.list(p), + retrieve: (s) => (id) => s.refunds.retrieve(id), + supportsCreatedFilter: true, + sync: true, + }, + checkout_sessions: { + order: 17, + tableName: 'checkout_sessions', + dependencies: ['customer', 'subscription', 'payment_intent', 'invoice'], + list: (s) => (p) => s.checkout.sessions.list(p), + retrieve: (s) => (id) => s.checkout.sessions.retrieve(id), + listExpands: [{ lines: (s) => (id) => s.checkout.sessions.listLineItems(id, { limit: 100 }) }], + supportsCreatedFilter: true, + sync: true, + childTables: ['checkout_session_line_items'], + }, + active_entitlements: { + order: 18, + tableName: 'active_entitlements', + dependencies: ['customer'], + list: (s) => (p) => + s.entitlements.activeEntitlements.list(p as Stripe.Entitlements.ActiveEntitlementListParams), + retrieve: (s) => (id) => s.entitlements.activeEntitlements.retrieve(id), + supportsCreatedFilter: true, + sync: false, + }, + review: { + order: 19, + tableName: 'reviews', + dependencies: ['payment_intent', 'charge'], + list: (s) => (p) => s.reviews.list(p), + retrieve: (s) => (id) => s.reviews.retrieve(id), + supportsCreatedFilter: true, + sync: false, + }, +} satisfies Record + +// Union of all object keys defined in RESOURCE_MAP. Used as the canonical object-name type across sync and registry helpers. +export type StripeObject = keyof typeof RESOURCE_MAP + +// Sync-enabled objects derived from RESOURCE_MAP metadata. +// Used for default full-sync selection and SyncObjectName composition. +export const CORE_SYNC_OBJECTS = Object.keys(RESOURCE_MAP).filter( + (k) => RESOURCE_MAP[k].sync +) as StripeObject[] + +// Type for one sync-enabled object key (excludes pseudo objects). +// Used where callers must operate on concrete sync resources only. +export type CoreSyncObject = (typeof CORE_SYNC_OBJECTS)[number] + +// Public sync object options including pseudo entries like "all". +// Used by sync input typing/validation for object selection. +export const SYNC_OBJECTS = ['all', 'customer_with_entitlements', ...CORE_SYNC_OBJECTS] as const -// Resource registry - maps SyncObject → list/retrieve operations -// Upsert is handled universally via StripeSync.upsertAny() -// Order field determines sync sequence - parents before children for FK dependencies +// Type of valid sync object input values. +// Used by exported config/types and CLI/object selection paths. +export type SyncObjectName = (typeof SYNC_OBJECTS)[number] + +// Entity names accepted for webhook revalidation overrides. +// Used by StripeSyncConfig.revalidateObjectsViaStripeApi typing. +export const REVALIDATE_ENTITIES = [ + ...Object.keys(RESOURCE_MAP), + 'radar.early_fraud_warning', + 'subscription_schedule', + 'entitlements', +] as const +// Type for a single revalidation entity name. +// Used by RevalidateEntity in shared sync config types. +export type RevalidateEntityName = (typeof REVALIDATE_ENTITIES)[number] + +// Tables that must exist for runtime sync and webhook processing. +// Used by migration/spec filtering to assert required schema coverage. +export const RUNTIME_REQUIRED_TABLES: ReadonlyArray = Array.from( + new Set([ + ...Object.values(RESOURCE_MAP).map((r) => r.tableName), + ...Object.values(RESOURCE_MAP).flatMap((r) => r.childTables ?? []), + 'features', // from customer_with_entitlements + ]) +) + +// Canonical table names for each RESOURCE_MAP object key. +// Used by OpenAPI/runtime adapters to avoid duplicating table mappings. +export const RESOURCE_TABLE_NAME_MAP = Object.fromEntries( + Object.entries(RESOURCE_MAP).map(([objectName, def]) => [objectName, def.tableName]) +) as Record + +// Builds runtime ResourceConfig objects from RESOURCE_MAP + Stripe client. +// Used by StripeSync constructor to initialize this.resourceRegistry. export function buildResourceRegistry(stripe: Stripe): Record { - const core: Record = { - product: { - order: 1, - tableName: 'products', - dependencies: [], - listFn: (p) => stripe.products.list(p), - retrieveFn: (id) => stripe.products.retrieve(id), - supportsCreatedFilter: true, - sync: true, - }, - price: { - order: 2, - tableName: 'prices', - dependencies: ['product'], - listFn: (p) => stripe.prices.list(p), - retrieveFn: (id) => stripe.prices.retrieve(id), - supportsCreatedFilter: true, - sync: true, - }, - plan: { - order: 3, - tableName: 'plans', - dependencies: ['product'], - listFn: (p) => stripe.plans.list(p), - retrieveFn: (id) => stripe.plans.retrieve(id), - supportsCreatedFilter: true, - sync: true, - }, - customer: { - order: 4, - tableName: 'customers', - dependencies: [], - listFn: (p) => stripe.customers.list(p), - retrieveFn: (id) => stripe.customers.retrieve(id), - supportsCreatedFilter: true, - sync: true, - isFinalState: (customer: Stripe.Customer | Stripe.DeletedCustomer) => - 'deleted' in customer && customer.deleted === true, - }, - subscription: { - order: 5, - tableName: 'subscriptions', - dependencies: ['customer', 'price'], - listFn: (p) => stripe.subscriptions.list(p), - retrieveFn: (id) => stripe.subscriptions.retrieve(id), - listExpands: [ - { items: (id) => stripe.subscriptionItems.list({ subscription: id, limit: 100 }) }, - ], - supportsCreatedFilter: true, - sync: true, - isFinalState: (subscription: Stripe.Subscription) => - subscription.status === 'canceled' || subscription.status === 'incomplete_expired', - }, - subscription_schedules: { - order: 6, - tableName: 'subscription_schedules', - dependencies: ['customer'], - listFn: (p) => stripe.subscriptionSchedules.list(p), - retrieveFn: (id) => stripe.subscriptionSchedules.retrieve(id), - supportsCreatedFilter: true, - sync: true, - isFinalState: (schedule: Stripe.SubscriptionSchedule) => - schedule.status === 'canceled' || schedule.status === 'completed', - }, - invoice: { - order: 7, - tableName: 'invoices', - dependencies: ['customer', 'subscription'], - listFn: (p) => stripe.invoices.list(p), - retrieveFn: (id) => stripe.invoices.retrieve(id), - listExpands: [{ lines: (id) => stripe.invoices.listLineItems(id, { limit: 100 }) }], - supportsCreatedFilter: true, - sync: true, - isFinalState: (invoice: Stripe.Invoice) => invoice.status === 'void', - }, - charge: { - order: 8, - tableName: 'charges', - dependencies: ['customer', 'invoice'], - listFn: (p) => stripe.charges.list(p), - retrieveFn: (id) => stripe.charges.retrieve(id), - listExpands: [{ refunds: (id) => stripe.refunds.list({ charge: id, limit: 100 }) }], - supportsCreatedFilter: true, - sync: true, - isFinalState: (charge: Stripe.Charge) => - charge.status === 'failed' || charge.status === 'succeeded', - }, - setup_intent: { - order: 9, - tableName: 'setup_intents', - dependencies: ['customer'], - listFn: (p) => stripe.setupIntents.list(p), - retrieveFn: (id) => stripe.setupIntents.retrieve(id), - supportsCreatedFilter: true, - sync: true, - isFinalState: (setupIntent: Stripe.SetupIntent) => - setupIntent.status === 'canceled' || setupIntent.status === 'succeeded', - }, - payment_method: { - order: 10, - tableName: 'payment_methods', - dependencies: ['customer'], - listFn: (p) => stripe.paymentMethods.list(p), - retrieveFn: (id) => stripe.paymentMethods.retrieve(id), - supportsCreatedFilter: false, - sync: true, - }, - payment_intent: { - order: 11, - tableName: 'payment_intents', - dependencies: ['customer', 'invoice'], - listFn: (p) => stripe.paymentIntents.list(p), - retrieveFn: (id) => stripe.paymentIntents.retrieve(id), - supportsCreatedFilter: true, - sync: true, - isFinalState: (paymentIntent: Stripe.PaymentIntent) => - paymentIntent.status === 'canceled' || paymentIntent.status === 'succeeded', - }, - tax_id: { - order: 12, - tableName: 'tax_ids', - dependencies: ['customer'], - listFn: (p) => stripe.taxIds.list(p), - retrieveFn: (id) => stripe.taxIds.retrieve(id), - supportsCreatedFilter: false, - sync: true, - }, - credit_note: { - order: 13, - tableName: 'credit_notes', - dependencies: ['customer', 'invoice'], - listFn: (p) => stripe.creditNotes.list(p), - retrieveFn: (id) => stripe.creditNotes.retrieve(id), - listExpands: [{ lines: (id) => stripe.creditNotes.listLineItems(id, { limit: 100 }) }], - supportsCreatedFilter: true, - sync: true, - isFinalState: (creditNote: Stripe.CreditNote) => creditNote.status === 'void', - }, - dispute: { - order: 14, - tableName: 'disputes', - dependencies: ['charge'], - listFn: (p) => stripe.disputes.list(p), - retrieveFn: (id) => stripe.disputes.retrieve(id), - supportsCreatedFilter: true, - sync: true, - isFinalState: (dispute: Stripe.Dispute) => - dispute.status === 'won' || dispute.status === 'lost', - }, - early_fraud_warning: { - order: 15, - tableName: 'early_fraud_warnings', - dependencies: ['payment_intent', 'charge'], - listFn: (p) => stripe.radar.earlyFraudWarnings.list(p), - retrieveFn: (id) => stripe.radar.earlyFraudWarnings.retrieve(id), - supportsCreatedFilter: true, - sync: true, - }, - refund: { - order: 16, - tableName: 'refunds', - dependencies: ['payment_intent', 'charge'], - listFn: (p) => stripe.refunds.list(p), - retrieveFn: (id) => stripe.refunds.retrieve(id), - supportsCreatedFilter: true, - sync: true, - }, - checkout_sessions: { - order: 17, - tableName: 'checkout_sessions', - dependencies: ['customer', 'subscription', 'payment_intent', 'invoice'], - listFn: (p) => stripe.checkout.sessions.list(p), - retrieveFn: (id) => stripe.checkout.sessions.retrieve(id), - supportsCreatedFilter: true, - sync: true, - listExpands: [{ lines: (id) => stripe.checkout.sessions.listLineItems(id, { limit: 100 }) }], - }, - active_entitlements: { - order: 18, - tableName: 'active_entitlements', - dependencies: ['customer'], - listFn: (p) => - stripe.entitlements.activeEntitlements.list( - p as unknown as Stripe.Entitlements.ActiveEntitlementListParams + return Object.fromEntries( + Object.entries(RESOURCE_MAP).map(([key, def]) => { + const config: StripeListResourceConfig = { + order: def.order, + tableName: def.tableName, + supportsCreatedFilter: def.supportsCreatedFilter, + sync: def.sync, + dependencies: def.dependencies ? [...def.dependencies] : [], + isFinalState: def.isFinalState, + listFn: def.list(stripe), + retrieveFn: def.retrieve(stripe), + listExpands: def.listExpands?.map((expand) => + Object.fromEntries(Object.entries(expand).map(([prop, fn]) => [prop, fn(stripe)])) ), - retrieveFn: (id) => stripe.entitlements.activeEntitlements.retrieve(id), - supportsCreatedFilter: true, - sync: false, - }, - review: { - order: 19, - tableName: 'reviews', - dependencies: ['payment_intent', 'charge'], - listFn: (p) => stripe.reviews.list(p), - retrieveFn: (id) => stripe.reviews.retrieve(id), - supportsCreatedFilter: true, - sync: false, - }, - } - - return core + } + return [key, config] + }) + ) as Record } -/** - * Build a separate registry for Sigma-backed resources. - * Order values start after the highest core order so backfill sequencing is preserved. - */ +// Builds Sigma registry entries ordered after core resource ordering. +// Used by StripeSync constructor to initialize this.sigmaRegistry. export function buildSigmaRegistry( sigma: SigmaSyncProcessor, coreRegistry: Record @@ -242,35 +300,29 @@ export function buildSigmaRegistry( return sigma.buildSigmaRegistryEntries(maxOrder) } -/** - * Maps Stripe API object type strings (e.g. "checkout.session") to SyncObject keys - * used in resourceRegistry and getTableName(). - */ -const STRIPE_OBJECT_TO_SYNC_OBJECT: Record = { +// Alias map from Stripe event object names to internal registry keys. +// Used by normalizeStripeObjectName during webhook/upsert ingestion. +export const STRIPE_OBJECT_TO_SYNC_OBJECT_ALIASES: Record = { 'checkout.session': 'checkout_sessions', 'radar.early_fraud_warning': 'early_fraud_warning', 'entitlements.active_entitlement': 'active_entitlements', - 'entitlements.feature': 'features', + 'entitlements.feature': 'active_entitlements', subscription_schedule: 'subscription_schedules', } -/** - * Convert a Stripe API object name (e.g. "checkout.session") to a SyncObject-compatible key. - * Handles dotted names like "checkout.session" -> "checkout_sessions". - * For simple names, returns as-is (e.g. "customer" -> "customer"). - */ +// Converts Stripe object names into canonical RESOURCE_MAP keys. +// Used before config/table lookups in webhook and sync flows. export function normalizeStripeObjectName(stripeObjectName: string): StripeObject { - return (STRIPE_OBJECT_TO_SYNC_OBJECT[stripeObjectName] ?? stripeObjectName) as StripeObject + const normalizedObjectName = + STRIPE_OBJECT_TO_SYNC_OBJECT_ALIASES[stripeObjectName] ?? stripeObjectName + return normalizedObjectName as StripeObject } -/** - * Maps Stripe ID prefixes to resource names used in the registry. - * Used to resolve a Stripe object ID (e.g. "cus_xxx") to its resource type. - * Prefixes are checked in order; longer prefixes should appear before shorter - * ones that share a common start (e.g. "issfr_" before "in_"). - */ -export const PREFIX_RESOURCE_MAP: Record = { +// Maps Stripe ID prefixes (e.g. cus_) to registry object names. +// Used when we only have an ID and need to resolve resource type. +export const PREFIX_RESOURCE_MAP: Record = { cus_: 'customer', + gcus_: 'customer', in_: 'invoice', price_: 'price', prod_: 'product', @@ -286,26 +338,23 @@ export const PREFIX_RESOURCE_MAP: Record = { issfr_: 'early_fraud_warning', prv_: 'review', re_: 'refund', - feat_: 'entitlements_feature', + feat_: 'active_entitlements', cs_: 'checkout_sessions', } -// Prefixes sorted longest-first so e.g. "issfr_" is tested before "in_" +// Prefixes sorted longest-first to avoid partial-prefix collisions. +// Used by getResourceFromPrefix for deterministic prefix matching. const SORTED_PREFIXES = Object.keys(PREFIX_RESOURCE_MAP).sort((a, b) => b.length - a.length) -/** - * Resolve a Stripe object ID (e.g. "cus_abc123") to its resource name - * in the registry (e.g. "customer"). Returns undefined if the prefix - * is not recognized. - */ +// Resolves a Stripe ID string to a registry object key by prefix. +// Used by getResourceConfigFromId and single-entity sync routing. export function getResourceFromPrefix(stripeId: string): string | undefined { const prefix = SORTED_PREFIXES.find((p) => stripeId.startsWith(p)) - return prefix ? PREFIX_RESOURCE_MAP[prefix] : undefined + return prefix ? (PREFIX_RESOURCE_MAP[prefix] as string) : undefined } -/** - * Get the resource configuration for a given Stripe ID. - */ +// Gets ResourceConfig for a raw Stripe ID like cus_/ch_/pi_. +// Used by StripeSync.syncSingleEntity to pick retrieve/upsert behavior. export function getResourceConfigFromId( stripeId: string, registry: Record @@ -314,13 +363,10 @@ export function getResourceConfigFromId( return resourceName ? registry[resourceName] : undefined } -/** - * Get the database table name for a SyncObject type from the resource registry. - */ +// Resolves table name for a canonical object key in a registry. +// Used by webhook and worker paths before writing to Postgres. export function getTableName(object: string, registry: Record): string { const config = registry[object] - if (!config) { - throw new Error(`No resource config found for object type: ${object}`) - } + if (!config) throw new Error(`No resource config found for object type: ${object}`) return config.tableName } diff --git a/packages/sync-engine/src/stripeSync.ts b/packages/sync-engine/src/stripeSync.ts index 197b7658..2a21b795 100644 --- a/packages/sync-engine/src/stripeSync.ts +++ b/packages/sync-engine/src/stripeSync.ts @@ -64,6 +64,18 @@ export class StripeSync { return this.sigma.sigmaSchemaName } + private get dataSchemaName(): string { + return this.config.schemaName ?? 'stripe' + } + + private get syncMetadataSchemaName(): string { + return this.config.syncTablesSchemaName ?? this.dataSchemaName + } + + private quoteSyncMetadataSchemaName(): string { + return `"${this.syncMetadataSchemaName.replaceAll('"', '""')}"` + } + private disableLogger() { this.savedLogger = this.config.logger ?? null this.config.logger = { info() {}, warn() {}, error() {} } @@ -101,7 +113,8 @@ export class StripeSync { const poolConfig = buildPoolConfig(config) this.postgresClient = new PostgresClient({ - schema: 'stripe', + schema: this.dataSchemaName, + syncSchema: this.syncMetadataSchemaName, poolConfig, }) @@ -593,9 +606,10 @@ export class StripeSync { subscriptionId: string, currentSubItemIds: string[] ): Promise<{ rowCount: number }> { + const schema = this.quoteSyncMetadataSchemaName() // deleted is a generated column that may be NULL for non-deleted items let prepared = sql(` - select id from "stripe"."subscription_items" + select id from ${schema}."subscription_items" where subscription = :subscriptionId and COALESCE(deleted, false) = false; `)({ subscriptionId }) const { rows } = await this.postgresClient.query(prepared.text, prepared.values) @@ -608,7 +622,7 @@ export class StripeSync { // Since deleted is a generated column, we need to update raw_data instead // Use jsonb_set to set the deleted field to true in the raw_data JSON prepared = sql(` - update "stripe"."subscription_items" + update ${schema}."subscription_items" set _raw_data = jsonb_set(_raw_data, '{deleted}', 'true'::jsonb) where id=any(:ids::text[]); `)({ ids }) @@ -655,8 +669,9 @@ export class StripeSync { await this.postgresClient.pool.end() } async printProgress(runKey: RunKey): Promise { + const schema = this.quoteSyncMetadataSchemaName() const syncQuery = { - text: `SELECT * FROM "stripe"."sync_obj_progress" + text: `SELECT * FROM ${schema}."sync_obj_progress" WHERE account_id = $1 AND run_started_at = $2 ORDER BY object`, values: [runKey.accountId, runKey.runStartedAt], diff --git a/packages/sync-engine/src/stripeSyncWebhook.ts b/packages/sync-engine/src/stripeSyncWebhook.ts index 55cc7b21..9396a5a9 100644 --- a/packages/sync-engine/src/stripeSyncWebhook.ts +++ b/packages/sync-engine/src/stripeSyncWebhook.ts @@ -29,12 +29,21 @@ export type StripeSyncWebhookDeps = { export class StripeSyncWebhook { constructor(private readonly deps: StripeSyncWebhookDeps) {} + private get syncMetadataSchemaName(): string { + return this.deps.config.syncTablesSchemaName ?? this.deps.config.schemaName ?? 'stripe' + } + + private quoteSyncMetadataSchemaName(): string { + return `"${this.syncMetadataSchemaName.replaceAll('"', '""')}"` + } + async processWebhook(payload: Buffer | Uint8Array | string, signature: string | undefined) { let webhookSecret: string | undefined = this.deps.config.stripeWebhookSecret if (!webhookSecret) { + const schema = this.quoteSyncMetadataSchemaName() const result = await this.deps.postgresClient.query( - `SELECT secret FROM "stripe"."_managed_webhooks" WHERE account_id = $1 LIMIT 1`, + `SELECT secret FROM ${schema}."_managed_webhooks" WHERE account_id = $1 LIMIT 1`, [this.deps.accountId] ) @@ -305,24 +314,27 @@ export class StripeSyncWebhook { } async getManagedWebhook(id: string): Promise { + const schema = this.quoteSyncMetadataSchemaName() const result = await this.deps.postgresClient.query( - `SELECT * FROM "stripe"."_managed_webhooks" WHERE id = $1 AND "account_id" = $2`, + `SELECT * FROM ${schema}."_managed_webhooks" WHERE id = $1 AND "account_id" = $2`, [id, this.deps.accountId] ) return result.rows.length > 0 ? (result.rows[0] as Stripe.WebhookEndpoint) : null } async getManagedWebhookByUrl(url: string): Promise { + const schema = this.quoteSyncMetadataSchemaName() const result = await this.deps.postgresClient.query( - `SELECT * FROM "stripe"."_managed_webhooks" WHERE url = $1 AND "account_id" = $2`, + `SELECT * FROM ${schema}."_managed_webhooks" WHERE url = $1 AND "account_id" = $2`, [url, this.deps.accountId] ) return result.rows.length > 0 ? (result.rows[0] as Stripe.WebhookEndpoint) : null } async listManagedWebhooks(): Promise> { + const schema = this.quoteSyncMetadataSchemaName() const result = await this.deps.postgresClient.query( - `SELECT * FROM "stripe"."_managed_webhooks" WHERE "account_id" = $1 ORDER BY created DESC`, + `SELECT * FROM ${schema}."_managed_webhooks" WHERE "account_id" = $1 ORDER BY created DESC`, [this.deps.accountId] ) return result.rows as Array diff --git a/packages/sync-engine/src/supabase/edge-functions/sigma-data-worker.ts b/packages/sync-engine/src/supabase/edge-functions/sigma-data-worker.ts index 0dee91d3..aac05a54 100644 --- a/packages/sync-engine/src/supabase/edge-functions/sigma-data-worker.ts +++ b/packages/sync-engine/src/supabase/edge-functions/sigma-data-worker.ts @@ -28,6 +28,9 @@ Deno.serve(async (req) => { if (!dbUrl) { return jsonResponse({ error: 'SUPABASE_DB_URL not set' }, 500) } + const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' + const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName + const safeSyncSchema = syncTablesSchemaName.replace(/"/g, '""') let sql: ReturnType | undefined let stripeSync: StripeSync | undefined @@ -69,6 +72,8 @@ Deno.serve(async (req) => { stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!, enableSigma: true, sigmaPageSizeOverride: 1000, + schemaName, + syncTablesSchemaName, }) } catch (error) { await sql.end() @@ -103,7 +108,7 @@ Deno.serve(async (req) => { // Legacy cleanup: remove any prefixed sigma object runs that can block concurrency. // Previous versions stored objects as "sigma." which no longer matches processNext. await stripeSync.postgresClient.query( - `UPDATE "stripe"."_sync_obj_runs" + `UPDATE "${safeSyncSchema}"."_sync_obj_runs" SET status = 'error', error_message = 'Legacy sigma worker prefix run (sigma.*); superseded by unprefixed runs', completed_at = now() @@ -175,7 +180,7 @@ Deno.serve(async (req) => { }) let selfTriggered = false try { - await sql`SELECT stripe.trigger_sigma_worker()` + await sql.unsafe(`SELECT "${safeSyncSchema}".trigger_sigma_worker()`) selfTriggered = true } catch (error) { console.warn('Failed to self-trigger sigma worker:', error.message) @@ -268,7 +273,7 @@ Deno.serve(async (req) => { remainingMinutes, }) try { - await sql`SELECT stripe.trigger_sigma_worker()` + await sql.unsafe(`SELECT "${safeSyncSchema}".trigger_sigma_worker()`) selfTriggered = true } catch (error) { console.warn('Failed to self-trigger sigma worker:', error.message) diff --git a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts index 5d588225..583982ce 100644 --- a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts +++ b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts @@ -107,10 +107,11 @@ Deno.serve(async (req) => { sql = postgres(dbUrl, { max: 1, prepare: false }) // Query installation status from schema comment + const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' const commentResult = await sql` SELECT obj_description(oid, 'pg_namespace') as comment FROM pg_namespace - WHERE nspname = 'stripe' + WHERE nspname = ${schemaName} ` const comment = commentResult[0]?.comment || null @@ -131,14 +132,16 @@ Deno.serve(async (req) => { let syncStatus = [] if (comment) { try { - syncStatus = await sql` + const syncSchema = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName + const safeSchema = syncSchema.replace(/"/g, '""') + syncStatus = await sql.unsafe(` SELECT DISTINCT ON (account_id) account_id, started_at, closed_at, status, error_message, total_processed, total_objects, complete_count, error_count, running_count, pending_count, triggered_by, max_concurrent - FROM stripe.sync_runs + FROM "${safeSchema}"."sync_runs" ORDER BY account_id, started_at DESC - ` + `) } catch (err) { // Ignore errors if sync_runs view doesn't exist yet console.warn('sync_runs query failed (may not exist yet):', err) @@ -194,9 +197,13 @@ Deno.serve(async (req) => { } // Step 1: Delete Stripe webhooks and clean up database + const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' + const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName stripeSync = await StripeSync.create({ poolConfig: { connectionString: dbUrl, max: 2 }, stripeSecretKey: stripeKey, + schemaName, + syncTablesSchemaName, }) // Delete all managed webhooks @@ -243,33 +250,38 @@ Deno.serve(async (req) => { // Drop Sigma self-trigger function if present try { - await stripeSync.postgresClient.query(` - DROP FUNCTION IF EXISTS stripe.trigger_sigma_worker(); - `) + const dropSchema = syncTablesSchemaName.replace(/"/g, '""') + await stripeSync.postgresClient.query( + `DROP FUNCTION IF EXISTS "${dropSchema}".trigger_sigma_worker()` + ) } catch (err) { console.warn('Could not drop sigma trigger function:', err) } - // Terminate connections holding locks on stripe schema + // Terminate connections holding locks on schema try { - await stripeSync.postgresClient.query(` - SELECT pg_terminate_backend(pid) - FROM pg_locks l - JOIN pg_class c ON l.relation = c.oid - JOIN pg_namespace n ON c.relnamespace = n.oid - WHERE n.nspname = 'stripe' - AND l.pid != pg_backend_pid() - `) + await stripeSync.postgresClient.query( + `SELECT pg_terminate_backend(pid) + FROM pg_locks l + JOIN pg_class c ON l.relation = c.oid + JOIN pg_namespace n ON c.relnamespace = n.oid + WHERE n.nspname = $1 AND l.pid != pg_backend_pid()`, + [syncTablesSchemaName] + ) } catch (err) { console.warn('Could not terminate connections:', err) } - // Drop schema with retry + // Drop schema(s) with retry + const schemasToDrop = [...new Set([schemaName, syncTablesSchemaName])] let dropAttempts = 0 const maxAttempts = 3 while (dropAttempts < maxAttempts) { try { - await stripeSync.postgresClient.query('DROP SCHEMA IF EXISTS stripe CASCADE') + for (const s of schemasToDrop) { + const safe = s.replace(/"/g, '""') + await stripeSync.postgresClient.query(`DROP SCHEMA IF EXISTS "${safe}" CASCADE`) + } break // Success, exit loop } catch (err) { dropAttempts++ @@ -372,15 +384,21 @@ Deno.serve(async (req) => { } const enableSigma = (Deno.env.get('ENABLE_SIGMA') ?? 'false') === 'true' + const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' + const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName await runMigrations({ databaseUrl: dbUrl, enableSigma, stripeApiVersion: Deno.env.get('STRIPE_API_VERSION') ?? '2020-08-27', + schemaName, + syncTablesSchemaName, }) stripeSync = await StripeSync.create({ poolConfig: { connectionString: dbUrl, max: 2 }, // Need 2 for advisory lock + queries stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY'), + schemaName, + syncTablesSchemaName, }) // Release any stale advisory locks from previous timeouts diff --git a/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts b/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts index 0ab7815d..07d0aa0f 100644 --- a/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts +++ b/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts @@ -11,6 +11,8 @@ Deno.serve(async (req) => { } const dbUrl = Deno.env.get('SUPABASE_DB_URL') + const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' + const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName if (!dbUrl) { return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 }) } @@ -19,6 +21,8 @@ Deno.serve(async (req) => { poolConfig: { connectionString: dbUrl, max: 1 }, stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!, partnerId: 'pp_supabase', + schemaName, + syncTablesSchemaName, }) try { diff --git a/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts b/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts index 126023c6..e23a25fe 100644 --- a/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts +++ b/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts @@ -16,6 +16,8 @@ const dbUrl = Deno.env.get('SUPABASE_DB_URL') const SYNC_INTERVAL = Number(Deno.env.get('SYNC_INTERVAL')) || 60 * 60 * 24 * 7 // Once a week default const rateLimit = Number(Deno.env.get('RATE_LIMIT')) || 60 const workerCount = Number(Deno.env.get('WORKER_COUNT')) || 10 +const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' +const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName const sql = postgres(dbUrl, { max: 1, prepare: false }) const stripeSync = await StripeSync.create({ @@ -23,6 +25,8 @@ const stripeSync = await StripeSync.create({ stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!, enableSigma: (Deno.env.get('ENABLE_SIGMA') ?? 'false') === 'true', partnerId: 'pp_supabase', + schemaName, + syncTablesSchemaName, }) const objects = stripeSync.getSupportedSyncObjects() const tableNames = objects.map((obj) => stripeSync.resourceRegistry[obj].tableName) diff --git a/packages/sync-engine/src/syncObjects.ts b/packages/sync-engine/src/syncObjects.ts deleted file mode 100644 index 170f82cc..00000000 --- a/packages/sync-engine/src/syncObjects.ts +++ /dev/null @@ -1,97 +0,0 @@ -export const CORE_SYNC_OBJECTS = [ - 'customer', - 'invoice', - 'price', - 'product', - 'subscription', - 'subscription_schedules', - 'setup_intent', - 'payment_method', - 'dispute', - 'charge', - 'payment_intent', - 'plan', - 'tax_id', - 'credit_note', - 'early_fraud_warning', - 'review', - 'refund', - 'checkout_sessions', -] as const - -export type CoreSyncObject = (typeof CORE_SYNC_OBJECTS)[number] - -export const SYNC_OBJECTS = ['all', 'customer_with_entitlements', ...CORE_SYNC_OBJECTS] as const -export type SyncObjectName = (typeof SYNC_OBJECTS)[number] - -export const REVALIDATE_ENTITIES = [ - 'charge', - 'credit_note', - 'customer', - 'dispute', - 'invoice', - 'payment_intent', - 'payment_method', - 'plan', - 'price', - 'product', - 'refund', - 'review', - 'radar.early_fraud_warning', - 'setup_intent', - 'subscription', - 'subscription_schedule', - 'tax_id', - 'entitlements', -] as const - -export type RevalidateEntityName = (typeof REVALIDATE_ENTITIES)[number] - -type SyncObjectSchemaTableMap = { - all: [] - customer_with_entitlements: readonly ['customers', 'features', 'active_entitlements'] -} & Record - -const SYNC_OBJECT_SCHEMA_TABLES: SyncObjectSchemaTableMap = { - all: [], - customer_with_entitlements: ['customers', 'features', 'active_entitlements'], - customer: ['customers'], - invoice: ['invoices'], - price: ['prices'], - product: ['products'], - subscription: ['subscriptions', 'subscription_items'], - subscription_schedules: ['subscription_schedules'], - setup_intent: ['setup_intents'], - payment_method: ['payment_methods'], - dispute: ['disputes'], - charge: ['charges'], - payment_intent: ['payment_intents'], - plan: ['plans'], - tax_id: ['tax_ids'], - credit_note: ['credit_notes'], - early_fraud_warning: ['early_fraud_warnings'], - review: ['reviews'], - refund: ['refunds'], - checkout_sessions: ['checkout_sessions', 'checkout_session_line_items'], -} - -export const SYNC_OBJECT_TO_RESOURCE_TABLE: Record = - CORE_SYNC_OBJECTS.reduce( - (resourceMap, objectName) => { - resourceMap[objectName] = SYNC_OBJECT_SCHEMA_TABLES[objectName][0] - return resourceMap - }, - {} as Record - ) - -export const RUNTIME_REQUIRED_TABLES: ReadonlyArray = Array.from( - new Set(SYNC_OBJECTS.flatMap((objectName) => SYNC_OBJECT_SCHEMA_TABLES[objectName])) -) - -export function getResourceNameForSyncObject(object: CoreSyncObject): string { - return SYNC_OBJECT_TO_RESOURCE_TABLE[object] -} - -export function isCoreSyncObject(object: string): object is CoreSyncObject { - return Object.hasOwn(SYNC_OBJECT_TO_RESOURCE_TABLE, object) -} diff --git a/packages/sync-engine/src/types.ts b/packages/sync-engine/src/types.ts index 731ac88c..574eb4df 100644 --- a/packages/sync-engine/src/types.ts +++ b/packages/sync-engine/src/types.ts @@ -1,6 +1,7 @@ import { type PoolConfig } from 'pg' import Stripe from 'stripe' import type { SigmaIngestionConfig } from './sigma/sigmaIngestion' +import type { RevalidateEntityName, SyncObjectName } from './resourceRegistry' /** * Simple logger interface compatible with both pino and console @@ -11,25 +12,7 @@ export interface Logger { error(message?: unknown, ...optionalParams: unknown[]): void } -export type RevalidateEntity = - | 'charge' - | 'credit_note' - | 'customer' - | 'dispute' - | 'invoice' - | 'payment_intent' - | 'payment_method' - | 'plan' - | 'price' - | 'product' - | 'refund' - | 'review' - | 'radar.early_fraud_warning' - | 'setup_intent' - | 'subscription' - | 'subscription_schedule' - | 'tax_id' - | 'entitlements' +export type RevalidateEntity = RevalidateEntityName export type StripeSyncConfig = { /** @deprecated Use `poolConfig` with a connection string instead. */ @@ -57,6 +40,18 @@ export type StripeSyncConfig = { */ sigmaSchemaName?: string + /** + * Postgres schema name for core Stripe data tables. + * Default: "stripe" + */ + schemaName?: string + + /** + * Postgres schema name for sync metadata tables (accounts, _sync_runs, _managed_webhooks, etc.). + * Defaults to schemaName when not provided. + */ + syncTablesSchemaName?: string + /** Stripe account ID. If not provided, will be retrieved from Stripe API. Used as fallback option. */ stripeAccountId?: string @@ -103,26 +98,7 @@ export type StripeSyncConfig = { maxConcurrentCustomers?: number } -export type SyncObject = - | 'all' - | 'customer' - | 'customer_with_entitlements' - | 'invoice' - | 'price' - | 'product' - | 'subscription' - | 'subscription_schedules' - | 'setup_intent' - | 'payment_method' - | 'dispute' - | 'charge' - | 'payment_intent' - | 'plan' - | 'tax_id' - | 'credit_note' - | 'early_fraud_warning' - | 'refund' - | 'checkout_sessions' +export type SyncObject = SyncObjectName export const SUPPORTED_WEBHOOK_EVENTS: Stripe.WebhookEndpointCreateParams.EnabledEvent[] = [ 'charge.captured', @@ -292,7 +268,7 @@ export type BaseResourceConfig = { /** Whether this resource is included in sync runs by default. Default: true */ sync?: boolean /** Resource types that must be backfilled before this one (e.g. price depends on product) */ - dependencies?: string[] + dependencies?: readonly string[] /** Function to check if an entity is in a final state and doesn't need revalidation */ // eslint-disable-next-line @typescript-eslint/no-explicit-any isFinalState?: (entity: any) => boolean From bfa6aa4509a0b9c75ffd5efe00f2e040be70cf74 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:12:59 -0500 Subject: [PATCH 05/11] fix --- packages/sync-engine/src/database/migrate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index 565c1f5c..0280d34f 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -378,9 +378,9 @@ async function insertMigrationMarker( } const idResult = await client.query( - `SELECT COALESCE(MIN(id), 0) - 1 as next_id FROM "${schema}"."_migrations"` + `SELECT COALESCE(MAX(id), -1) + 1 as next_id FROM "${schema}"."_migrations"` ) - const nextId = Number(idResult.rows[0]?.next_id ?? -1) + const nextId = Number(idResult.rows[0]?.next_id ?? 0) await client.query( `INSERT INTO "${schema}"."_migrations" (id, name, hash) VALUES ($1, $2, $3) ON CONFLICT (name) DO NOTHING`, [nextId, marker, hash] From 7502292b5d97c77e515de0cd16c8c453b89a7098 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:17:23 -0500 Subject: [PATCH 06/11] fix ci --- packages/sync-engine/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sync-engine/package.json b/packages/sync-engine/package.json index 2b208711..11cbe825 100644 --- a/packages/sync-engine/package.json +++ b/packages/sync-engine/package.json @@ -34,7 +34,7 @@ "test:e2e": "pnpm run build && vitest run --config vitest.e2e.config.ts && vitest run --config vitest.e2e.webhook.config.ts", "start": "pnpm run build && node dist/cli/index.js backfill", "full-sync": "pnpm run build && node dist/cli/index.js full-sync", - "generate:sigma-schema": "tsx src/sigma/schema/fetch-schema.ts", + "generate:sigma-schema": "tsx src/sigma/schema/fetch-schema.ts" }, "files": [ "dist" From 462b5ef3b5d2a90a17e1c167b2878e4dbad2c683 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:19:07 -0500 Subject: [PATCH 07/11] readd migration bundling --- packages/sync-engine/package.json | 2 +- .../sync-engine/scripts/build-functions.ts | 33 ++ packages/sync-engine/src/database/migrate.ts | 175 +++++++++++ .../src/database/migrations-embedded.ts | 290 ++++++++++++++++++ packages/sync-engine/src/index.ts | 4 +- .../supabase/edge-functions/stripe-setup.ts | 19 +- 6 files changed, 513 insertions(+), 10 deletions(-) create mode 100644 packages/sync-engine/src/database/migrations-embedded.ts diff --git a/packages/sync-engine/package.json b/packages/sync-engine/package.json index 11cbe825..81126915 100644 --- a/packages/sync-engine/package.json +++ b/packages/sync-engine/package.json @@ -26,7 +26,7 @@ "scripts": { "clean": "rimraf dist", "prebuild": "npm run clean && npm run build:functions", - "build:functions": "tsx scripts/build-functions.ts", + "build:functions": "tsx scripts/build-functions.ts && prettier --write src/database/migrations-embedded.ts", "build": "tsup src/index.ts src/supabase/index.ts src/cli/index.ts src/cli/lib.ts --format esm,cjs --dts --shims && cp -r src/database/migrations dist/migrations", "lint": "eslint src --ext .ts", "test": "vitest", diff --git a/packages/sync-engine/scripts/build-functions.ts b/packages/sync-engine/scripts/build-functions.ts index 8b63a9c8..b6626927 100644 --- a/packages/sync-engine/scripts/build-functions.ts +++ b/packages/sync-engine/scripts/build-functions.ts @@ -10,6 +10,37 @@ const rootDir = path.resolve(__dirname, '..') const srcDir = path.join(rootDir, 'src/supabase/edge-functions') const outDir = path.join(rootDir, 'dist/supabase/functions') +export function generateEmbeddedMigrations() { + const migrationsDir = path.join(rootDir, 'src/database/migrations') + const outFile = path.join(rootDir, 'src/database/migrations-embedded.ts') + + const files = fs + .readdirSync(migrationsDir) + .filter((f) => f.endsWith('.sql')) + .sort() + + const migrations = files.map((filename) => ({ + name: filename, + sql: fs.readFileSync(path.join(migrationsDir, filename), 'utf-8'), + })) + + const lines = [ + '// AUTO-GENERATED by scripts/build-functions.ts — do not edit manually.', + '// Run `tsx scripts/build-functions.ts` (or the build) to regenerate.', + '', + 'export type EmbeddedMigration = {', + ' name: string', + ' sql: string', + '}', + '', + `export const embeddedMigrations: EmbeddedMigration[] = ${JSON.stringify(migrations, null, 2)}`, + '', + ] + + fs.writeFileSync(outFile, lines.join('\n'), 'utf-8') + console.log(`Generated ${outFile} with ${migrations.length} migrations`) +} + if (!fs.existsSync(srcDir)) { console.error(`Source directory not found: ${srcDir}`) process.exit(1) @@ -24,6 +55,8 @@ const files = fs.readdirSync(srcDir).filter((f) => f.endsWith('.ts')) console.log(`Found ${files.length} functions in ${srcDir}`) async function build() { + generateEmbeddedMigrations() + for (const file of files) { const name = path.basename(file, '.ts') const entryPoint = path.join(srcDir, file) diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index 0280d34f..c31dfd30 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -1,6 +1,7 @@ import { Client } from 'pg' import { migrate } from 'pg-node-migrations' import { Buffer } from 'node:buffer' +import crypto from 'node:crypto' import fs from 'node:fs' import path from 'node:path' import { createHash } from 'node:crypto' @@ -17,6 +18,7 @@ import { WritePathPlanner, resolveOpenApiSpec, } from '../openapi' +import type { EmbeddedMigration } from './migrations-embedded' const DEFAULT_STRIPE_API_VERSION = '2020-08-27' const SIGMA_BASE_COLUMNS = ['_raw_data', '_last_synced_at', '_updated_at', '_account_id'] as const @@ -568,3 +570,176 @@ export async function runMigrations(config: MigrationConfig): Promise { config.logger?.info('Finished migrations') } } + +// Helper to parse migration ID from filename (matches pg-node-migrations behavior) +function parseMigrationId(fileName: string): number { + const match = /^(-?\d+)[-_]?/.exec(fileName) + if (!match) { + throw new Error(`Invalid migration file name: '${fileName}'`) + } + return parseInt(match[1], 10) +} + +// Helper to compute hash matching pg-node-migrations format +function computeMigrationHash(fileName: string, sql: string): string { + return crypto + .createHash('sha1') + .update(fileName + sql, 'utf8') + .digest('hex') +} + +type ParsedMigration = { + id: number + name: string + fileName: string + sql: string + hash: string +} + +function parseMigrations(migrations: EmbeddedMigration[]): ParsedMigration[] { + return migrations + .map((migration) => ({ + id: parseMigrationId(migration.name), + name: migration.name.replace(/^\d+[-_]?/, '').replace(/\.sql$/, '') || migration.name, + fileName: migration.name, + sql: migration.sql, + hash: computeMigrationHash(migration.name, migration.sql), + })) + .sort((a, b) => a.id - b.id) +} + +async function ensureMigrationsTable( + client: Client, + schema: string, + tableName: string +): Promise { + await client.query(` + CREATE TABLE IF NOT EXISTS "${schema}"."${tableName}" ( + id integer PRIMARY KEY, + name varchar(100) UNIQUE NOT NULL, + hash varchar(40) NOT NULL, + executed_at timestamp DEFAULT current_timestamp + ) + `) +} + +async function getAppliedMigrations( + client: Client, + schema: string, + tableName: string +): Promise<{ id: number; name: string; hash: string }[]> { + const tableExists = await doesTableExist(client, schema, tableName) + if (!tableExists) { + return [] + } + const result = await client.query( + `SELECT id, name, hash FROM "${schema}"."${tableName}" ORDER BY id` + ) + return result.rows +} + +async function runMigration( + client: Client, + schema: string, + tableName: string, + migration: ParsedMigration, + logger?: Logger +): Promise { + logger?.info(`Running migration: ${migration.id} ${migration.name}`) + + await client.query('BEGIN') + try { + await client.query(migration.sql) + await client.query( + `INSERT INTO "${schema}"."${tableName}" (id, name, hash) VALUES ($1, $2, $3)`, + [migration.id, migration.name, migration.hash] + ) + await client.query('COMMIT') + } catch (err) { + await client.query('ROLLBACK') + throw err + } +} + +/** + * Run migrations from embedded content (for edge runtimes without filesystem migrations access). + * This is compatible with pg-node-migrations table format. + */ +export async function runMigrationsFromContent( + config: MigrationConfig, + migrations: EmbeddedMigration[] +): Promise { + const client = new Client({ + connectionString: config.databaseUrl, + ssl: config.ssl, + connectionTimeoutMillis: 10_000, + }) + const dataSchema = config.schemaName ?? 'stripe' + const syncSchema = config.syncTablesSchemaName ?? dataSchema + const defaultSchema = 'stripe' + const tableName = '_migrations' + + if (dataSchema !== defaultSchema || syncSchema !== defaultSchema) { + throw new Error( + `Custom schema migrations are no longer supported. Use "${defaultSchema}" for both schemaName and syncTablesSchemaName.` + ) + } + + try { + config.logger?.info('Starting migrations (from embedded content)') + await client.connect() + await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(defaultSchema)}`) + await renameMigrationsTableIfNeeded(client, syncSchema, config.logger) + + const tableExists = await doesTableExist(client, syncSchema, tableName) + if (tableExists) { + const migrationCount = await client.query( + `SELECT COUNT(*) as count FROM "${syncSchema}"."${tableName}"` + ) + const isEmpty = migrationCount.rows[0]?.count === '0' + if (isEmpty) { + await cleanupSchema(client, syncSchema, config.logger) + } + } + + await ensureMigrationsTable(client, syncSchema, tableName) + + const appliedMigrations = await getAppliedMigrations(client, syncSchema, tableName) + const appliedIds = new Set(appliedMigrations.map((migration) => migration.id)) + const parsedMigrations = parseMigrations(migrations) + + for (const applied of appliedMigrations) { + const intended = parsedMigrations.find((migration) => migration.id === applied.id) + if (intended && intended.hash !== applied.hash) { + throw new Error( + `Migration hash mismatch for ${applied.name}: ` + + `expected ${intended.hash}, got ${applied.hash}. ` + + `Migrations cannot be modified after being applied.` + ) + } + } + + const pendingMigrations = parsedMigrations.filter((migration) => !appliedIds.has(migration.id)) + if (pendingMigrations.length === 0) { + config.logger?.info('No migrations to run') + } else { + config.logger?.info(`Running ${pendingMigrations.length} migration(s)`) + for (const migration of pendingMigrations) { + await runMigration(client, syncSchema, tableName, migration, config.logger) + } + config.logger?.info(`Successfully applied ${pendingMigrations.length} migration(s)`) + } + + await applyOpenApiSchema(client, config, dataSchema, syncSchema) + + if (config.enableSigma) { + await migrateSigmaSchema(client, config, 'sigma', syncSchema) + } + } catch (err) { + config.logger?.error(err, 'Error running migrations from content') + throw err + } finally { + await client.end() + config.logger?.info('Finished migrations') + } +} diff --git a/packages/sync-engine/src/database/migrations-embedded.ts b/packages/sync-engine/src/database/migrations-embedded.ts new file mode 100644 index 00000000..07076bef --- /dev/null +++ b/packages/sync-engine/src/database/migrations-embedded.ts @@ -0,0 +1,290 @@ +// AUTO-GENERATED by scripts/build-functions.ts — do not edit manually. +// Run `tsx scripts/build-functions.ts` (or the build) to regenerate. + +export type EmbeddedMigration = { + name: string + sql: string +} + +export const embeddedMigrations: EmbeddedMigration[] = [ + { + name: '0000_initial_migration.sql', + sql: 'select 1;', + }, + { + name: '0001_products.sql', + sql: 'create table if not exists "stripe"."products" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "description" text,\n "metadata" jsonb,\n "name" text,\n "created" integer,\n "images" jsonb,\n "livemode" boolean,\n "package_dimensions" jsonb,\n "shippable" boolean,\n "statement_descriptor" text,\n "unit_label" text,\n "updated" integer,\n "url" text\n);\n', + }, + { + name: '0002_customers.sql', + sql: 'create table if not exists "stripe"."customers" (\n "id" text primary key,\n "object" text,\n "address" jsonb,\n "description" text,\n "email" text,\n "metadata" jsonb,\n "name" text,\n "phone" text,\n "shipping" jsonb,\n "balance" integer,\n "created" integer,\n "currency" text,\n "default_source" text,\n "delinquent" boolean,\n "discount" jsonb,\n "invoice_prefix" text,\n "invoice_settings" jsonb,\n "livemode" boolean,\n "next_invoice_sequence" integer,\n "preferred_locales" jsonb,\n "tax_exempt" text\n);\n', + }, + { + name: '0003_prices.sql', + sql: 'DO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_type\') THEN\n create type "stripe"."pricing_type" as enum (\'one_time\', \'recurring\');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_tiers\') THEN\n create type "stripe"."pricing_tiers" as enum (\'graduated\', \'volume\');\n END IF;\n --more types here...\nEND\n$$;\n\n\ncreate table if not exists "stripe"."prices" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "currency" text,\n "metadata" jsonb,\n "nickname" text,\n "recurring" jsonb,\n "type" stripe.pricing_type,\n "unit_amount" integer,\n "billing_scheme" text,\n "created" integer,\n "livemode" boolean,\n "lookup_key" text,\n "tiers_mode" stripe.pricing_tiers,\n "transform_quantity" jsonb,\n "unit_amount_decimal" text,\n\n "product" text references stripe.products\n);\n\n', + }, + { + name: '0004_subscriptions.sql', + sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'subscription_status\') THEN\n create type "stripe"."subscription_status" as enum (\n \'trialing\',\n \'active\',\n \'canceled\',\n \'incomplete\',\n \'incomplete_expired\',\n \'past_due\',\n \'unpaid\'\n );\n END IF;\nEND\n$$;\n\ncreate table if not exists "stripe"."subscriptions" (\n "id" text primary key,\n "object" text,\n "cancel_at_period_end" boolean,\n "current_period_end" integer,\n "current_period_start" integer,\n "default_payment_method" text,\n "items" jsonb,\n "metadata" jsonb,\n "pending_setup_intent" text,\n "pending_update" jsonb,\n "status" "stripe"."subscription_status", \n "application_fee_percent" double precision,\n "billing_cycle_anchor" integer,\n "billing_thresholds" jsonb,\n "cancel_at" integer,\n "canceled_at" integer,\n "collection_method" text,\n "created" integer,\n "days_until_due" integer,\n "default_source" text,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "ended_at" integer,\n "livemode" boolean,\n "next_pending_invoice_item_invoice" integer,\n "pause_collection" jsonb,\n "pending_invoice_item_interval" jsonb,\n "start_date" integer,\n "transfer_data" jsonb,\n "trial_end" jsonb,\n "trial_start" jsonb,\n\n "schedule" text,\n "customer" text references "stripe"."customers",\n "latest_invoice" text, -- not yet joined\n "plan" text -- not yet joined\n);\n\n', + }, + { + name: '0005_invoices.sql', + sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'invoice_status\') THEN\n create type "stripe"."invoice_status" as enum (\'draft\', \'open\', \'paid\', \'uncollectible\', \'void\');\n END IF;\nEND\n$$;\n\n\ncreate table if not exists "stripe"."invoices" (\n "id" text primary key,\n "object" text,\n "auto_advance" boolean,\n "collection_method" text,\n "currency" text,\n "description" text,\n "hosted_invoice_url" text,\n "lines" jsonb,\n "metadata" jsonb,\n "period_end" integer,\n "period_start" integer,\n "status" "stripe"."invoice_status",\n "total" bigint,\n "account_country" text,\n "account_name" text,\n "account_tax_ids" jsonb,\n "amount_due" bigint,\n "amount_paid" bigint,\n "amount_remaining" bigint,\n "application_fee_amount" bigint,\n "attempt_count" integer,\n "attempted" boolean,\n "billing_reason" text,\n "created" integer,\n "custom_fields" jsonb,\n "customer_address" jsonb,\n "customer_email" text,\n "customer_name" text,\n "customer_phone" text,\n "customer_shipping" jsonb,\n "customer_tax_exempt" text,\n "customer_tax_ids" jsonb,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "discounts" jsonb,\n "due_date" integer,\n "ending_balance" integer,\n "footer" text,\n "invoice_pdf" text,\n "last_finalization_error" jsonb,\n "livemode" boolean,\n "next_payment_attempt" integer,\n "number" text,\n "paid" boolean,\n "payment_settings" jsonb,\n "post_payment_credit_notes_amount" integer,\n "pre_payment_credit_notes_amount" integer,\n "receipt_number" text,\n "starting_balance" integer,\n "statement_descriptor" text,\n "status_transitions" jsonb,\n "subtotal" integer,\n "tax" integer,\n "total_discount_amounts" jsonb,\n "total_tax_amounts" jsonb,\n "transfer_data" jsonb,\n "webhooks_delivered_at" integer,\n\n "customer" text references "stripe"."customers",\n "subscription" text references "stripe"."subscriptions",\n "payment_intent" text, -- not yet implemented\n "default_payment_method" text, -- not yet implemented\n "default_source" text, -- not yet implemented\n "on_behalf_of" text, -- not yet implemented\n "charge" text -- not yet implemented\n);\n', + }, + { + name: '0006_charges.sql', + sql: '\ncreate table if not exists "stripe".charges (\n id text primary key,\n object text,\n card jsonb,\n paid boolean,\n "order" text,\n amount bigint,\n review text,\n source jsonb,\n status text,\n created integer,\n dispute text,\n invoice text,\n outcome jsonb,\n refunds jsonb,\n updated integer,\n captured boolean,\n currency text,\n customer text,\n livemode boolean,\n metadata jsonb,\n refunded boolean,\n shipping jsonb,\n application text,\n description text,\n destination text,\n failure_code text,\n on_behalf_of text,\n fraud_details jsonb,\n receipt_email text,\n payment_intent text,\n receipt_number text,\n transfer_group text,\n amount_refunded bigint,\n application_fee text,\n failure_message text,\n source_transfer text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n payment_method_details jsonb\n);\n', + }, + { + name: '0007_coupons.sql', + sql: 'create table if not exists "stripe".coupons (\n id text primary key,\n object text,\n name text,\n valid boolean,\n created integer,\n updated integer,\n currency text,\n duration text,\n livemode boolean,\n metadata jsonb,\n redeem_by integer,\n amount_off bigint,\n percent_off double precision,\n times_redeemed bigint,\n max_redemptions bigint,\n duration_in_months bigint,\n percent_off_precise double precision\n);\n', + }, + { + name: '0008_disputes.sql', + sql: 'create table if not exists "stripe".disputes (\n id text primary key,\n object text,\n amount bigint,\n charge text,\n reason text,\n status text,\n created integer,\n updated integer,\n currency text,\n evidence jsonb,\n livemode boolean,\n metadata jsonb,\n evidence_details jsonb,\n balance_transactions jsonb,\n is_charge_refundable boolean\n);\n', + }, + { + name: '0009_events.sql', + sql: 'create table if not exists "stripe".events (\n id text primary key,\n object text,\n data jsonb,\n type text,\n created integer,\n request text,\n updated integer,\n livemode boolean,\n api_version text,\n pending_webhooks bigint\n);\n', + }, + { + name: '0010_payouts.sql', + sql: 'create table if not exists "stripe".payouts (\n id text primary key,\n object text,\n date text,\n type text,\n amount bigint,\n method text,\n status text,\n created integer,\n updated integer,\n currency text,\n livemode boolean,\n metadata jsonb,\n automatic boolean,\n recipient text,\n description text,\n destination text,\n source_type text,\n arrival_date text,\n bank_account jsonb,\n failure_code text,\n transfer_group text,\n amount_reversed bigint,\n failure_message text,\n source_transaction text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n failure_balance_transaction text\n);\n', + }, + { + name: '0011_plans.sql', + sql: 'create table if not exists "stripe"."plans" (\n id text primary key,\n object text,\n name text,\n tiers jsonb,\n active boolean,\n amount bigint,\n created integer,\n product text,\n updated integer,\n currency text,\n "interval" text,\n livemode boolean,\n metadata jsonb,\n nickname text,\n tiers_mode text,\n usage_type text,\n billing_scheme text,\n interval_count bigint,\n aggregate_usage text,\n transform_usage text,\n trial_period_days bigint,\n statement_descriptor text,\n statement_description text\n);\n', + }, + { + name: '0012_add_updated_at.sql', + sql: "create or replace function set_updated_at() returns trigger\n language plpgsql\nas\n$$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\nalter table stripe.subscriptions\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.subscriptions\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.products\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.products\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.customers\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.customers\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.prices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.prices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.invoices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.invoices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.charges\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.charges\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.coupons\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.coupons\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.disputes\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.disputes\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.events\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.events\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.payouts\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.payouts\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.plans\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.plans\n for each row\n execute procedure set_updated_at();\n", + }, + { + name: '0013_add_subscription_items.sql', + sql: 'create table if not exists "stripe"."subscription_items" (\n "id" text primary key,\n "object" text,\n "billing_thresholds" jsonb,\n "created" integer,\n "deleted" boolean,\n "metadata" jsonb,\n "quantity" integer,\n "price" text references "stripe"."prices",\n "subscription" text references "stripe"."subscriptions",\n "tax_rates" jsonb\n);', + }, + { + name: '0014_migrate_subscription_items.sql', + sql: 'WITH subscriptions AS (\n select jsonb_array_elements(items->\'data\') as obj from "stripe"."subscriptions"\n)\ninsert into "stripe"."subscription_items"\nselect obj->>\'id\' as "id",\n obj->>\'object\' as "object", \n obj->\'billing_thresholds\' as "billing_thresholds", \n (obj->>\'created\')::INTEGER as "created", \n (obj->>\'deleted\')::BOOLEAN as "deleted", \n obj->\'metadata\' as "metadata", \n (obj->>\'quantity\')::INTEGER as "quantity", \n (obj->\'price\'->>\'id\')::TEXT as "price", \n obj->>\'subscription\' as "subscription", \n obj->\'tax_rates\' as "tax_rates"\nfrom subscriptions\non conflict ("id") \ndo update set "id" = excluded."id",\n "object" = excluded."object",\n "billing_thresholds" = excluded."billing_thresholds",\n "created" = excluded."created",\n "deleted" = excluded."deleted",\n "metadata" = excluded."metadata",\n "quantity" = excluded."quantity",\n "price" = excluded."price",\n "subscription" = excluded."subscription",\n "tax_rates" = excluded."tax_rates"', + }, + { + name: '0015_add_customer_deleted.sql', + sql: 'alter table stripe.customers\n add deleted boolean default false not null;', + }, + { + name: '0016_add_invoice_indexes.sql', + sql: 'CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);', + }, + { + name: '0017_drop_charges_unavailable_columns.sql', + sql: '-- drop columns that are duplicated / not available anymore\n-- card is not available on webhook v.2020-03-02. We can get the detail from payment_method_details\n-- statement_description is not available on webhook v.2020-03-02\nalter table "stripe"."charges"\n drop column if exists "card",\n drop column if exists "statement_description";', + }, + { + name: '0018_setup_intents.sql', + sql: 'create table if not exists "stripe"."setup_intents" (\n id text primary key,\n object text,\n created integer,\n customer text,\n description text,\n payment_method text,\n status text,\n usage text,\n cancellation_reason text,\n latest_attempt text,\n mandate text,\n single_use_mandate text,\n on_behalf_of text\n);\n\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);', + }, + { + name: '0019_payment_methods.sql', + sql: 'create table if not exists "stripe"."payment_methods" (\n id text primary key,\n object text,\n created integer,\n customer text,\n type text,\n billing_details jsonb,\n metadata jsonb,\n card jsonb\n);\n\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);', + }, + { + name: '0020_disputes_payment_intent_created_idx.sql', + sql: 'ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS payment_intent TEXT;\n\nCREATE INDEX IF NOT EXISTS stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);', + }, + { + name: '0021_payment_intent.sql', + sql: 'create table if not exists "stripe"."payment_intents" (\n id text primary key,\n object text,\n amount integer,\n amount_capturable integer,\n amount_details jsonb,\n amount_received integer,\n application text,\n application_fee_amount integer,\n automatic_payment_methods text,\n canceled_at integer,\n cancellation_reason text,\n capture_method text,\n client_secret text,\n confirmation_method text,\n created integer,\n currency text,\n customer text,\n description text,\n invoice text,\n last_payment_error text,\n livemode boolean,\n metadata jsonb,\n next_action text,\n on_behalf_of text,\n payment_method text,\n payment_method_options jsonb,\n payment_method_types jsonb,\n processing text,\n receipt_email text,\n review text,\n setup_future_usage text,\n shipping jsonb,\n statement_descriptor text,\n statement_descriptor_suffix text,\n status text,\n transfer_data jsonb,\n transfer_group text\n);\n\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);', + }, + { + name: '0022_adjust_plans.sql', + sql: 'ALTER TABLE if exists "stripe"."plans" DROP COLUMN name;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN updated;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN tiers;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_descriptor;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_description;', + }, + { + name: '0023_invoice_deleted.sql', + sql: 'ALTER TYPE "stripe"."invoice_status" ADD VALUE \'deleted\';', + }, + { + name: '0024_subscription_schedules.sql', + sql: "do $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'subscription_schedule_status') THEN\n create type \"stripe\".\"subscription_schedule_status\" as enum ('not_started', 'active', 'completed', 'released', 'canceled');\n END IF;\nEND\n$$;\n\ncreate table if not exists\n \"stripe\".\"subscription_schedules\" (\n id text primary key,\n object text,\n application text,\n canceled_at integer,\n completed_at integer,\n created integer not null,\n current_phase jsonb,\n customer text not null,\n default_settings jsonb,\n end_behavior text,\n livemode boolean not null,\n metadata jsonb not null,\n phases jsonb not null,\n released_at integer,\n released_subscription text,\n status stripe.subscription_schedule_status not null,\n subscription text,\n test_clock text\n );", + }, + { + name: '0025_tax_ids.sql', + sql: 'create table if not exists\n "stripe"."tax_ids" (\n "id" text primary key,\n "object" text,\n "country" text,\n "customer" text,\n "type" text,\n "value" text,\n "created" integer not null,\n "livemode" boolean,\n "owner" jsonb\n );\n\ncreate index stripe_tax_ids_customer_idx on "stripe"."tax_ids" using btree (customer);', + }, + { + name: '0026_credit_notes.sql', + sql: 'create table if not exists\n "stripe"."credit_notes" (\n "id" text primary key,\n object text,\n amount integer,\n amount_shipping integer,\n created integer,\n currency text,\n customer text,\n customer_balance_transaction text,\n discount_amount integer,\n discount_amounts jsonb,\n invoice text,\n lines jsonb,\n livemode boolean,\n memo text,\n metadata jsonb,\n number text,\n out_of_band_amount integer,\n pdf text,\n reason text,\n refund text,\n shipping_cost jsonb,\n status text,\n subtotal integer,\n subtotal_excluding_tax integer,\n tax_amounts jsonb,\n total integer,\n total_excluding_tax integer,\n type text,\n voided_at text\n );\n\ncreate index stripe_credit_notes_customer_idx on "stripe"."credit_notes" using btree (customer);\n\ncreate index stripe_credit_notes_invoice_idx on "stripe"."credit_notes" using btree (invoice);', + }, + { + name: '0027_add_marketing_features_to_products.sql', + sql: 'ALTER TABLE IF EXISTS stripe.products ADD COLUMN IF NOT EXISTS marketing_features JSONB;\n\n', + }, + { + name: '0028_early_fraud_warning.sql', + sql: 'create table\n if not exists "stripe"."early_fraud_warnings" (\n "id" text primary key,\n object text,\n actionable boolean,\n charge text,\n created integer,\n fraud_type text,\n livemode boolean,\n payment_intent text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_early_fraud_warnings_charge_idx on "stripe"."early_fraud_warnings" using btree (charge);\n\ncreate index stripe_early_fraud_warnings_payment_intent_idx on "stripe"."early_fraud_warnings" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.early_fraud_warnings\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0029_reviews.sql', + sql: 'create table\n if not exists "stripe"."reviews" (\n "id" text primary key,\n object text,\n billing_zip text,\n charge text,\n created integer,\n closed_reason text,\n livemode boolean,\n ip_address text,\n ip_address_location jsonb,\n open boolean,\n opened_reason text,\n payment_intent text,\n reason text,\n session text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_reviews_charge_idx on "stripe"."reviews" using btree (charge);\n\ncreate index stripe_reviews_payment_intent_idx on "stripe"."reviews" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.reviews\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0030_refunds.sql', + sql: 'create table\n if not exists "stripe"."refunds" (\n "id" text primary key,\n object text,\n amount integer,\n balance_transaction text,\n charge text,\n created integer,\n currency text,\n destination_details jsonb,\n metadata jsonb,\n payment_intent text,\n reason text,\n receipt_number text,\n source_transfer_reversal text,\n status text,\n transfer_reversal text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_refunds_charge_idx on "stripe"."refunds" using btree (charge);\n\ncreate index stripe_refunds_payment_intent_idx on "stripe"."refunds" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.refunds\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0031_add_default_price.sql', + sql: 'alter table "stripe"."products"\nadd column IF NOT EXISTS "default_price" text;\n', + }, + { + name: '0032_update_subscription_items.sql', + sql: 'ALTER TABLE "stripe"."subscription_items"\nADD COLUMN IF NOT EXISTS "current_period_end" integer,\nADD COLUMN IF NOT EXISTS "current_period_start" integer;\n', + }, + { + name: '0033_add_last_synced_at.sql', + sql: '-- Add last_synced_at column to all Stripe tables for tracking sync status\n\n-- Charges\nalter table "stripe"."charges"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Coupons\nalter table "stripe"."coupons"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Credit Notes\nalter table "stripe"."credit_notes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Customers\nalter table "stripe"."customers"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Disputes\nalter table "stripe"."disputes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Early Fraud Warnings\nalter table "stripe"."early_fraud_warnings"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Events\nalter table "stripe"."events"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Invoices\nalter table "stripe"."invoices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Intents\nalter table "stripe"."payment_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Methods\nalter table "stripe"."payment_methods"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payouts\nalter table "stripe"."payouts"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Plans\nalter table "stripe"."plans"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Prices\nalter table "stripe"."prices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Products\nalter table "stripe"."products"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Refunds\nalter table "stripe"."refunds"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Reviews\nalter table "stripe"."reviews"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Setup Intents\nalter table "stripe"."setup_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Items\nalter table "stripe"."subscription_items"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Schedules\nalter table "stripe"."subscription_schedules"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscriptions\nalter table "stripe"."subscriptions"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Tax IDs\nalter table "stripe"."tax_ids"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n', + }, + { + name: '0034_remove_foreign_keys.sql', + sql: '-- Remove all foreign key constraints\n\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS "subscriptions_customer_fkey";\n\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS "prices_product_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_customer_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_subscription_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_price_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_subscription_fkey";\n', + }, + { + name: '0035_checkout_sessions.sql', + sql: 'create table\n if not exists "stripe"."checkout_sessions" (\n "id" text primary key,\n "object" text,\n "adaptive_pricing" jsonb,\n "after_expiration" jsonb,\n "allow_promotion_codes" boolean,\n "amount_subtotal" integer,\n "amount_total" integer,\n "automatic_tax" jsonb,\n "billing_address_collection" text,\n "cancel_url" text,\n "client_reference_id" text,\n "client_secret" text,\n "collected_information" jsonb,\n "consent" jsonb,\n "consent_collection" jsonb,\n "created" integer,\n "currency" text,\n "currency_conversion" jsonb,\n "custom_fields" jsonb,\n "custom_text" jsonb,\n "customer" text,\n "customer_creation" text,\n "customer_details" jsonb,\n "customer_email" text,\n "discounts" jsonb,\n "expires_at" integer,\n "invoice" text,\n "invoice_creation" jsonb,\n "livemode" boolean,\n "locale" text,\n "metadata" jsonb,\n "mode" text,\n "optional_items" jsonb,\n "payment_intent" text,\n "payment_link" text,\n "payment_method_collection" text,\n "payment_method_configuration_details" jsonb,\n "payment_method_options" jsonb,\n "payment_method_types" jsonb,\n "payment_status" text,\n "permissions" jsonb,\n "phone_number_collection" jsonb,\n "presentment_details" jsonb,\n "recovered_from" text,\n "redirect_on_completion" text,\n "return_url" text,\n "saved_payment_method_options" jsonb,\n "setup_intent" text,\n "shipping_address_collection" jsonb,\n "shipping_cost" jsonb,\n "shipping_details" jsonb,\n "shipping_options" jsonb,\n "status" text,\n "submit_type" text,\n "subscription" text,\n "success_url" text,\n "tax_id_collection" jsonb,\n "total_details" jsonb,\n "ui_mode" text,\n "url" text,\n "wallet_options" jsonb,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_checkout_sessions_customer_idx on "stripe"."checkout_sessions" using btree (customer);\ncreate index stripe_checkout_sessions_subscription_idx on "stripe"."checkout_sessions" using btree (subscription);\ncreate index stripe_checkout_sessions_payment_intent_idx on "stripe"."checkout_sessions" using btree (payment_intent);\ncreate index stripe_checkout_sessions_invoice_idx on "stripe"."checkout_sessions" using btree (invoice);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_sessions\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0036_checkout_session_line_items.sql', + sql: 'create table if not exists "stripe"."checkout_session_line_items" (\n "id" text primary key,\n "object" text,\n "amount_discount" integer,\n "amount_subtotal" integer,\n "amount_tax" integer,\n "amount_total" integer,\n "currency" text,\n "description" text,\n "price" text references "stripe"."prices" on delete cascade,\n "quantity" integer,\n "checkout_session" text references "stripe"."checkout_sessions" on delete cascade,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n);\n\ncreate index stripe_checkout_session_line_items_session_idx on "stripe"."checkout_session_line_items" using btree (checkout_session);\ncreate index stripe_checkout_session_line_items_price_idx on "stripe"."checkout_session_line_items" using btree (price);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_session_line_items\n for each row\n execute procedure set_updated_at(); ', + }, + { + name: '0037_add_features.sql', + sql: 'create table\n if not exists "stripe"."features" (\n "id" text primary key,\n object text,\n livemode boolean,\n name text,\n lookup_key text unique,\n active boolean,\n metadata jsonb,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null,\n last_synced_at timestamptz\n );\n\ncreate trigger handle_updated_at\n before update\n on stripe.features\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0038_active_entitlement.sql', + sql: 'create table\n if not exists "stripe"."active_entitlements" (\n "id" text primary key,\n "object" text,\n "livemode" boolean,\n "feature" text,\n "customer" text,\n "lookup_key" text unique,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_active_entitlements_customer_idx on "stripe"."active_entitlements" using btree (customer);\ncreate index stripe_active_entitlements_feature_idx on "stripe"."active_entitlements" using btree (feature);\n\ncreate trigger handle_updated_at\n before update\n on stripe.active_entitlements\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0039_add_paused_to_subscription_status.sql', + sql: 'ALTER TYPE "stripe"."subscription_status" ADD VALUE \'paused\';', + }, + { + name: '0040_managed_webhooks.sql', + sql: 'create table\n if not exists "stripe"."managed_webhooks" (\n "id" text primary key,\n "object" text,\n "uuid" text unique not null,\n "url" text not null,\n "enabled_events" jsonb not null,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text not null,\n "status" text,\n "api_version" text,\n "created" integer,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_managed_webhooks_uuid_idx on "stripe"."managed_webhooks" using btree (uuid);\ncreate index stripe_managed_webhooks_status_idx on "stripe"."managed_webhooks" using btree (status);\ncreate index stripe_managed_webhooks_enabled_idx on "stripe"."managed_webhooks" using btree (enabled);\n\ncreate trigger handle_updated_at\n before update\n on stripe.managed_webhooks\n for each row\n execute procedure set_updated_at();\n', + }, + { + name: '0041_rename_managed_webhooks.sql', + sql: '-- Rename managed_webhooks table to _managed_webhooks\nalter table if exists "stripe"."managed_webhooks" rename to "_managed_webhooks";\n', + }, + { + name: '0042_convert_to_jsonb_generated_columns.sql', + sql: '-- Convert all tables to use jsonb raw_data as source of truth with generated columns\n-- This migration adds raw_data column and converts all existing columns to generated columns\n\n-- ============================================================================\n-- ACTIVE_ENTITLEMENTS\n-- ============================================================================\n\n-- Add raw_data column\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes (will be recreated on generated columns)\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_feature_idx";\n\n-- Drop unique constraint (will be recreated on generated column)\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS "active_entitlements_lookup_key_key";\n\n-- Drop existing columns and recreate as generated columns\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((raw_data->>\'feature\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- CHARGES\n-- ============================================================================\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((raw_data->>\'order\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (raw_data->\'source\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((raw_data->>\'dispute\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (raw_data->\'outcome\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (raw_data->\'refunds\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((raw_data->>\'captured\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((raw_data->>\'refunded\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (raw_data->\'fraud_details\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_refunded\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((raw_data->>\'application_fee\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_details\') STORED;\n\n-- ============================================================================\n-- CHECKOUT_SESSION_LINE_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_session_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_price_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((raw_data->>\'amount_discount\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((raw_data->>\'amount_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((raw_data->>\'checkout_session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\n\n-- ============================================================================\n-- CHECKOUT_SESSIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_subscription_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_payment_intent_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_invoice_idx";\n\n-- Drop and recreate columns as generated (all columns from checkoutSessionSchema)\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (raw_data->\'adaptive_pricing\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (raw_data->\'after_expiration\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((raw_data->>\'allow_promotion_codes\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (raw_data->\'automatic_tax\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((raw_data->>\'billing_address_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((raw_data->>\'cancel_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((raw_data->>\'client_reference_id\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (raw_data->\'collected_information\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (raw_data->\'consent\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (raw_data->\'consent_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (raw_data->\'currency_conversion\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (raw_data->\'custom_text\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((raw_data->>\'customer_creation\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (raw_data->\'customer_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((raw_data->>\'expires_at\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_creation\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((raw_data->>\'locale\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((raw_data->>\'mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (raw_data->\'optional_items\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((raw_data->>\'payment_link\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((raw_data->>\'payment_method_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_configuration_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((raw_data->>\'payment_status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (raw_data->\'permissions\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (raw_data->\'phone_number_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (raw_data->\'presentment_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((raw_data->>\'recovered_from\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((raw_data->>\'redirect_on_completion\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((raw_data->>\'return_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'saved_payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_address_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((raw_data->>\'submit_type\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((raw_data->>\'success_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (raw_data->\'tax_id_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (raw_data->\'total_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((raw_data->>\'ui_mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (raw_data->\'wallet_options\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\n\n-- ============================================================================\n-- COUPONS\n-- ============================================================================\n\nALTER TABLE "stripe"."coupons" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((raw_data->>\'valid\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((raw_data->>\'duration\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((raw_data->>\'redeem_by\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_off\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off\')::double precision) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((raw_data->>\'times_redeemed\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((raw_data->>\'max_redemptions\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((raw_data->>\'duration_in_months\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off_precise\')::double precision) STORED;\n\n-- ============================================================================\n-- CREDIT_NOTES\n-- ============================================================================\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((raw_data->>\'amount_shipping\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'customer_balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((raw_data->>\'discount_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'discount_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((raw_data->>\'memo\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((raw_data->>\'out_of_band_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((raw_data->>\'pdf\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((raw_data->>\'refund\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'tax_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((raw_data->>\'total\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'total_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((raw_data->>\'voided_at\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\n\n-- ============================================================================\n-- CUSTOMERS\n-- ============================================================================\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (raw_data->\'address\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((raw_data->>\'phone\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((raw_data->>\'balance\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((raw_data->>\'delinquent\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((raw_data->>\'invoice_prefix\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_settings\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((raw_data->>\'next_invoice_sequence\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (raw_data->\'preferred_locales\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\n-- ============================================================================\n-- DISPUTES\n-- ============================================================================\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_dispute_created_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (raw_data->\'evidence\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (raw_data->\'evidence_details\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (raw_data->\'balance_transactions\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((raw_data->>\'is_charge_refundable\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\n\n-- ============================================================================\n-- EARLY_FRAUD_WARNINGS\n-- ============================================================================\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((raw_data->>\'actionable\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((raw_data->>\'fraud_type\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\n\n-- ============================================================================\n-- EVENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."events" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (raw_data->\'data\') STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((raw_data->>\'request\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((raw_data->>\'api_version\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((raw_data->>\'pending_webhooks\')::bigint) STORED;\n\n-- ============================================================================\n-- FEATURES\n-- ============================================================================\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop unique constraint\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS "features_lookup_key_key";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- INVOICES\n-- ============================================================================\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_subscription_idx";\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((raw_data->>\'auto_advance\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((raw_data->>\'hosted_invoice_url\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((raw_data->>\'period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((raw_data->>\'period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((raw_data->>\'total\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((raw_data->>\'account_country\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((raw_data->>\'account_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'account_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_due\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_paid\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_remaining\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((raw_data->>\'attempt_count\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((raw_data->>\'attempted\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((raw_data->>\'billing_reason\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (raw_data->\'customer_address\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((raw_data->>\'customer_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((raw_data->>\'customer_phone\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (raw_data->\'customer_shipping\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'customer_tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'customer_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((raw_data->>\'due_date\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((raw_data->>\'ending_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((raw_data->>\'footer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((raw_data->>\'invoice_pdf\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (raw_data->\'last_finalization_error\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((raw_data->>\'next_payment_attempt\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (raw_data->\'payment_settings\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((raw_data->>\'starting_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (raw_data->\'status_transitions\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((raw_data->>\'tax\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_discount_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_tax_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((raw_data->>\'webhooks_delivered_at\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\n\n-- ============================================================================\n-- PAYMENT_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((raw_data->>\'amount_capturable\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (raw_data->\'amount_details\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((raw_data->>\'amount_received\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((raw_data->>\'automatic_payment_methods\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((raw_data->>\'capture_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((raw_data->>\'confirmation_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((raw_data->>\'last_payment_error\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((raw_data->>\'next_action\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((raw_data->>\'processing\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((raw_data->>\'setup_future_usage\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor_suffix\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\n\n-- ============================================================================\n-- PAYMENT_METHODS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_methods_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (raw_data->\'billing_details\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (raw_data->\'card\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\n\n-- ============================================================================\n-- PAYOUTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payouts" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((raw_data->>\'date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((raw_data->>\'method\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((raw_data->>\'automatic\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((raw_data->>\'recipient\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((raw_data->>\'source_type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((raw_data->>\'arrival_date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (raw_data->\'bank_account\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_reversed\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((raw_data->>\'source_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'failure_balance_transaction\')::text) STORED;\n\n-- ============================================================================\n-- PLANS\n-- ============================================================================\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (raw_data->\'tiers\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((raw_data->>\'interval\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((raw_data->>\'usage_type\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((raw_data->>\'interval_count\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((raw_data->>\'aggregate_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((raw_data->>\'transform_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((raw_data->>\'trial_period_days\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\n-- ============================================================================\n-- PRICES\n-- ============================================================================\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum types converted to text)\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (raw_data->\'recurring\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((raw_data->>\'unit_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (raw_data->\'transform_quantity\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((raw_data->>\'unit_amount_decimal\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\n-- ============================================================================\n-- PRODUCTS\n-- ============================================================================\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((raw_data->>\'default_price\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (raw_data->\'images\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (raw_data->\'marketing_features\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (raw_data->\'package_dimensions\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((raw_data->>\'shippable\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((raw_data->>\'unit_label\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\n-- ============================================================================\n-- REFUNDS\n-- ============================================================================\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (raw_data->\'destination_details\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer_reversal\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'transfer_reversal\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\n\n-- ============================================================================\n-- REVIEWS\n-- ============================================================================\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((raw_data->>\'billing_zip\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((raw_data->>\'closed_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((raw_data->>\'ip_address\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (raw_data->\'ip_address_location\') STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((raw_data->>\'open\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((raw_data->>\'opened_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((raw_data->>\'session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\n\n-- ============================================================================\n-- SETUP_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_setup_intents_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((raw_data->>\'usage\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((raw_data->>\'latest_attempt\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((raw_data->>\'mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((raw_data->>\'single_use_mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\n\n-- ============================================================================\n-- SUBSCRIPTION_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTION_SCHEDULES\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((raw_data->>\'completed_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (raw_data->\'current_phase\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (raw_data->\'default_settings\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((raw_data->>\'end_behavior\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (raw_data->\'phases\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((raw_data->>\'released_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((raw_data->>\'released_subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((raw_data->>\'test_clock\')::text) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((raw_data->>\'cancel_at_period_end\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (raw_data->\'items\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'pending_setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (raw_data->\'pending_update\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((raw_data->>\'application_fee_percent\')::double precision) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((raw_data->>\'billing_cycle_anchor\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((raw_data->>\'cancel_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((raw_data->>\'days_until_due\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((raw_data->>\'ended_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (raw_data->\'pause_collection\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (raw_data->\'pending_invoice_item_interval\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((raw_data->>\'start_date\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (raw_data->\'trial_end\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (raw_data->\'trial_start\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((raw_data->>\'schedule\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((raw_data->>\'latest_invoice\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((raw_data->>\'plan\')::text) STORED;\n\n-- ============================================================================\n-- TAX_IDS\n-- ============================================================================\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_tax_ids_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((raw_data->>\'value\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (raw_data->\'owner\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n\n', + }, + { + name: '0043_add_account_id.sql', + sql: '-- Add _account_id column to all tables to track which Stripe account each record belongs to\n-- Column is nullable for backward compatibility with existing data\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."_managed_webhooks" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\n', + }, + { + name: '0044_make_account_id_required.sql', + sql: '-- Make _account_id required by:\n-- 1. Deleting all rows where _account_id IS NULL\n-- 2. Setting _account_id to NOT NULL\n\n-- Delete rows with null _account_id\nDELETE FROM "stripe"."active_entitlements" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."charges" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_session_line_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_sessions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."credit_notes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."customers" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."disputes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."early_fraud_warnings" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."features" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."invoices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."_managed_webhooks" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_methods" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."plans" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."prices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."products" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."refunds" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."reviews" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."setup_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_schedules" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscriptions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."tax_ids" WHERE "_account_id" IS NULL;\n\n-- Make _account_id NOT NULL\nALTER TABLE "stripe"."active_entitlements" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."charges" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_session_line_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_sessions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."credit_notes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."customers" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."disputes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."early_fraud_warnings" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."features" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."invoices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."_managed_webhooks" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_methods" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."plans" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."prices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."products" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."refunds" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."reviews" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."setup_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_schedules" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscriptions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."tax_ids" ALTER COLUMN "_account_id" SET NOT NULL;\n\n', + }, + { + name: '0045_sync_status.sql', + sql: "-- Create _sync_status metadata table for tracking incremental sync cursors\n-- This table tracks the state and progress of each resource's synchronization\n\nCREATE TABLE IF NOT EXISTS \"stripe\".\"_sync_status\" (\n id serial PRIMARY KEY,\n resource text UNIQUE NOT NULL,\n status text CHECK (status IN ('idle', 'running', 'complete', 'error')) DEFAULT 'idle',\n last_synced_at timestamptz DEFAULT now(),\n last_incremental_cursor timestamptz,\n error_message text,\n updated_at timestamptz DEFAULT now()\n);\n\n-- Use existing set_updated_at() function created in migration 0012\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON \"stripe\".\"_sync_status\"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n", + }, + { + name: '0046_sync_status_per_account.sql', + sql: '-- Add _account_id to _sync_status table to track sync cursors per account\n-- This enables proper cursor isolation when syncing multiple Stripe accounts\n--\n-- Breaking change: All existing cursor data will be deleted (clean slate)\n-- Next sync will perform a full backfill for each account\n\n-- Step 1: Delete all existing cursor data\nDELETE FROM "stripe"."_sync_status";\n\n-- Step 2: Add _account_id column\nALTER TABLE "stripe"."_sync_status" ADD COLUMN "_account_id" TEXT NOT NULL;\n\n-- Step 3: Drop existing unique constraint on resource\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_key;\n\n-- Step 4: Add new composite unique constraint on (resource, _account_id)\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "_account_id");\n\n-- Step 5: Add index for efficient lookups\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "_account_id");\n\n-- Step 6: Create accounts table to track Stripe accounts (JSONB with generated columns)\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n id TEXT PRIMARY KEY,\n raw_data JSONB NOT NULL,\n first_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n -- Generated columns extracted from raw_data\n business_name TEXT GENERATED ALWAYS AS ((raw_data->\'business_profile\'->>\'name\')::text) STORED,\n email TEXT GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED,\n type TEXT GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED,\n charges_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'charges_enabled\')::boolean) STORED,\n payouts_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'payouts_enabled\')::boolean) STORED,\n details_submitted BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'details_submitted\')::boolean) STORED,\n country TEXT GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED,\n default_currency TEXT GENERATED ALWAYS AS ((raw_data->>\'default_currency\')::text) STORED,\n created INTEGER GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED\n);\n\n-- Step 7: Add index for account name lookups\nCREATE INDEX IF NOT EXISTS idx_accounts_business_name\n ON "stripe"."accounts" (business_name);\n\n-- Step 8: Add updated_at trigger for accounts\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."accounts"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n\n-- Step 9: Backfill accounts from existing data tables\nINSERT INTO "stripe"."accounts" (id, raw_data, first_synced_at, last_synced_at)\nSELECT DISTINCT\n "_account_id" as id,\n jsonb_build_object(\'id\', "_account_id", \'type\', \'unknown\') as raw_data,\n now() as first_synced_at,\n now() as last_synced_at\nFROM "stripe"."products"\nWHERE "_account_id" IS NOT NULL\nON CONFLICT (id) DO NOTHING;\n\n-- Step 10: Add foreign key constraints from data tables to accounts\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Step 11: Add foreign key from _sync_status to accounts\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n', + }, + { + name: '0047_api_key_hashes.sql', + sql: '-- Add api_key_hashes array column to accounts table\n-- This stores SHA-256 hashes of Stripe API keys for fast account lookups\n-- Enables lookup of account ID by API key hash without making Stripe API calls\n-- Supports multiple API keys per account (test/live keys, rotated keys, etc.)\n\n-- Step 1: Add api_key_hashes column as TEXT array\nALTER TABLE "stripe"."accounts" ADD COLUMN "api_key_hashes" TEXT[] DEFAULT \'{}\';\n\n-- Step 2: Create GIN index for fast array containment lookups\n-- This enables efficient queries like: WHERE \'hash_value\' = ANY(api_key_hashes)\nCREATE INDEX IF NOT EXISTS idx_accounts_api_key_hashes\n ON "stripe"."accounts" USING GIN (api_key_hashes);\n', + }, + { + name: '0048_rename_reserved_columns.sql', + sql: '-- Rename all reserved column names to use underscore prefix\n-- This clearly distinguishes system-managed columns from user-accessible data\n--\n-- Changes:\n-- - id -> _id (primary key for all tables)\n-- - raw_data -> _raw_data (JSONB source of truth)\n-- - last_synced_at -> _last_synced_at (sync timestamp)\n-- - updated_at -> _updated_at (last update timestamp)\n-- - _account_id remains unchanged (already has underscore prefix)\n\n-- ============================================================================\n-- STEP 1: RENAME BASE COLUMNS FOR ALL TABLES\n-- ============================================================================\n\n-- This renames id, raw_data, last_synced_at, and updated_at for all tables\n-- PostgreSQL automatically updates primary keys, foreign keys, and indexes\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- charges\nALTER TABLE "stripe"."charges" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."charges" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."charges" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."charges" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- coupons\nALTER TABLE "stripe"."coupons" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- customers\nALTER TABLE "stripe"."customers" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."customers" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."customers" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."customers" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- disputes\nALTER TABLE "stripe"."disputes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- events\nALTER TABLE "stripe"."events" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."events" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."events" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."events" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- features\nALTER TABLE "stripe"."features" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."features" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."features" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."features" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- invoices\nALTER TABLE "stripe"."invoices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- _managed_webhooks\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payouts\nALTER TABLE "stripe"."payouts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- plans\nALTER TABLE "stripe"."plans" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."plans" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."plans" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."plans" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- prices\nALTER TABLE "stripe"."prices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."prices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."prices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."prices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- products\nALTER TABLE "stripe"."products" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."products" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."products" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."products" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- refunds\nALTER TABLE "stripe"."refunds" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- reviews\nALTER TABLE "stripe"."reviews" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- Metadata Tables\n\n-- _sync_status\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- accounts\nALTER TABLE "stripe"."accounts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- ============================================================================\n-- STEP 1.5: UPDATE TRIGGER FUNCTION TO USE NEW COLUMN NAME\n-- ============================================================================\n\n-- Now that all columns are renamed, update the trigger function to reference _updated_at\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new._updated_at = now();\n return NEW;\nend;\n$$;\n\n-- ============================================================================\n-- STEP 2: RECREATE GENERATED COLUMNS TO REFERENCE _raw_data\n-- ============================================================================\n\n-- All generated columns must be dropped and recreated to reference the new\n-- _raw_data column name instead of raw_data\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\n\n-- Add generated columns back with _raw_data references\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((_raw_data->>\'feature\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((_raw_data->>\'order\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (_raw_data->\'source\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((_raw_data->>\'dispute\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (_raw_data->\'outcome\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (_raw_data->\'refunds\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((_raw_data->>\'captured\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((_raw_data->>\'refunded\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (_raw_data->\'fraud_details\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_refunded\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((_raw_data->>\'application_fee\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_details\') STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((_raw_data->>\'checkout_session\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (_raw_data->\'adaptive_pricing\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (_raw_data->\'after_expiration\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((_raw_data->>\'allow_promotion_codes\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (_raw_data->\'automatic_tax\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((_raw_data->>\'billing_address_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((_raw_data->>\'cancel_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((_raw_data->>\'client_reference_id\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (_raw_data->\'collected_information\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (_raw_data->\'consent\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'consent_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (_raw_data->\'currency_conversion\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_text\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((_raw_data->>\'customer_creation\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_creation\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((_raw_data->>\'locale\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((_raw_data->>\'mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (_raw_data->\'optional_items\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((_raw_data->>\'payment_link\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_configuration_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((_raw_data->>\'payment_status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (_raw_data->\'permissions\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'phone_number_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (_raw_data->\'presentment_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((_raw_data->>\'recovered_from\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((_raw_data->>\'redirect_on_completion\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((_raw_data->>\'return_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'saved_payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'setup_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_address_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((_raw_data->>\'submit_type\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((_raw_data->>\'success_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_id_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (_raw_data->\'total_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((_raw_data->>\'ui_mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (_raw_data->\'wallet_options\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((_raw_data->>\'valid\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((_raw_data->>\'duration\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((_raw_data->>\'redeem_by\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_off\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off\')::double precision) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((_raw_data->>\'times_redeemed\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((_raw_data->>\'max_redemptions\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((_raw_data->>\'duration_in_months\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off_precise\')::double precision) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'customer_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discount_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((_raw_data->>\'memo\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((_raw_data->>\'pdf\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((_raw_data->>\'refund\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((_raw_data->>\'total\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((_raw_data->>\'voided_at\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (_raw_data->\'address\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((_raw_data->>\'email\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((_raw_data->>\'phone\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((_raw_data->>\'balance\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((_raw_data->>\'delinquent\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_prefix\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_settings\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((_raw_data->>\'next_invoice_sequence\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (_raw_data->\'preferred_locales\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence_details\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (_raw_data->\'balance_transactions\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((_raw_data->>\'is_charge_refundable\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((_raw_data->>\'actionable\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((_raw_data->>\'fraud_type\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (_raw_data->\'data\') STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((_raw_data->>\'request\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((_raw_data->>\'api_version\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((_raw_data->>\'pending_webhooks\')::bigint) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((_raw_data->>\'auto_advance\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((_raw_data->>\'hosted_invoice_url\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'period_end\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'period_start\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((_raw_data->>\'account_country\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((_raw_data->>\'account_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'account_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_due\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_paid\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_remaining\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((_raw_data->>\'attempt_count\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((_raw_data->>\'attempted\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((_raw_data->>\'billing_reason\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_address\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((_raw_data->>\'customer_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((_raw_data->>\'customer_phone\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_shipping\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'customer_tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((_raw_data->>\'due_date\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((_raw_data->>\'footer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_pdf\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (_raw_data->\'last_finalization_error\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((_raw_data->>\'next_payment_attempt\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_settings\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (_raw_data->\'status_transitions\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((_raw_data->>\'tax\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_discount_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_tax_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((_raw_data->>\'webhooks_delivered_at\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (_raw_data->\'amount_details\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((_raw_data->>\'automatic_payment_methods\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((_raw_data->>\'capture_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((_raw_data->>\'confirmation_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((_raw_data->>\'last_payment_error\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((_raw_data->>\'next_action\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((_raw_data->>\'processing\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((_raw_data->>\'setup_future_usage\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor_suffix\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_details\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (_raw_data->\'card\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((_raw_data->>\'date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((_raw_data->>\'method\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((_raw_data->>\'automatic\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((_raw_data->>\'recipient\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((_raw_data->>\'source_type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((_raw_data->>\'arrival_date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (_raw_data->\'bank_account\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_reversed\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'source_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'failure_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (_raw_data->\'tiers\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((_raw_data->>\'interval\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((_raw_data->>\'usage_type\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((_raw_data->>\'interval_count\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((_raw_data->>\'aggregate_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((_raw_data->>\'transform_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((_raw_data->>\'trial_period_days\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (_raw_data->\'recurring\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (_raw_data->\'transform_quantity\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((_raw_data->>\'unit_amount_decimal\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((_raw_data->>\'default_price\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (_raw_data->\'images\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (_raw_data->\'marketing_features\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (_raw_data->\'package_dimensions\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((_raw_data->>\'shippable\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((_raw_data->>\'unit_label\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (_raw_data->\'destination_details\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((_raw_data->>\'billing_zip\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((_raw_data->>\'closed_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((_raw_data->>\'ip_address\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (_raw_data->\'ip_address_location\') STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((_raw_data->>\'open\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((_raw_data->>\'opened_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((_raw_data->>\'session\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((_raw_data->>\'usage\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((_raw_data->>\'latest_attempt\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((_raw_data->>\'mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((_raw_data->>\'single_use_mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_rates\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((_raw_data->>\'completed_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (_raw_data->\'current_phase\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'default_settings\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((_raw_data->>\'end_behavior\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (_raw_data->\'phases\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((_raw_data->>\'released_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((_raw_data->>\'released_subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((_raw_data->>\'test_clock\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((_raw_data->>\'cancel_at_period_end\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (_raw_data->\'items\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'pending_setup_intent\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_update\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((_raw_data->>\'application_fee_percent\')::double precision) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((_raw_data->>\'billing_cycle_anchor\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((_raw_data->>\'cancel_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((_raw_data->>\'days_until_due\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((_raw_data->>\'ended_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((_raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'pause_collection\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_invoice_item_interval\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((_raw_data->>\'start_date\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_end\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_start\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((_raw_data->>\'schedule\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((_raw_data->>\'latest_invoice\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((_raw_data->>\'plan\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((_raw_data->>\'country\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((_raw_data->>\'value\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (_raw_data->\'owner\') STORED;\n\n-- ============================================================================\n-- STEP 3: RECREATE INDEXES\n-- ============================================================================\n\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n', + }, + { + name: '0049_remove_redundant_underscores_from_metadata_tables.sql', + sql: '-- Remove redundant underscore prefixes from columns in metadata tables\n--\n-- For tables that are already prefixed with underscore (indicating they are\n-- metadata/system tables), the underscore prefix on columns is redundant.\n-- This migration removes those redundant prefixes to keep naming cleaner.\n--\n-- Affected tables: _sync_status, _managed_webhooks\n\n-- Create a new trigger function for metadata tables that references updated_at without underscore\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\n-- Update _sync_status table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_status";\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_account_key;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\nDROP INDEX IF EXISTS "stripe"."idx_sync_status_resource_account";\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate constraints and trigger with new column names\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "account_id");\n\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "account_id");\n\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT fk_sync_status_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_status"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Update _managed_webhooks table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate foreign key constraint and trigger with new column names\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT fk_managed_webhooks_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_managed_webhooks"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n', + }, + { + name: '0050_rename_id_to_match_stripe_api.sql', + sql: '-- Rename _id back to id to match Stripe API field names\n--\n-- Migration 0048 added underscore prefixes to all "reserved" columns including id.\n-- However, id is actually a field that comes directly from the Stripe API and should\n-- match the API naming for agent/user comprehension.\n--\n-- Additionally, this migration converts id from a regular column to a GENERATED column\n-- derived from _raw_data->>\'id\', ensuring the raw_data is the single source of truth.\n\n-- ============================================================================\n-- Step 1: Drop all foreign key constraints referencing accounts._id\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS fk_active_entitlements_account;\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS fk_charges_account;\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS fk_checkout_session_line_items_account;\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS fk_checkout_sessions_account;\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS fk_credit_notes_account;\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS fk_customers_account;\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS fk_disputes_account;\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS fk_early_fraud_warnings_account;\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS fk_features_account;\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS fk_invoices_account;\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS fk_payment_intents_account;\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS fk_payment_methods_account;\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS fk_plans_account;\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS fk_prices_account;\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS fk_products_account;\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS fk_refunds_account;\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS fk_reviews_account;\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS fk_setup_intents_account;\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS fk_subscription_items_account;\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS fk_subscription_schedules_account;\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS fk_subscriptions_account;\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS fk_tax_ids_account;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\n\n-- ============================================================================\n-- Step 2: Convert accounts._id to generated column accounts.id\n-- ============================================================================\n\nALTER TABLE "stripe"."accounts" DROP CONSTRAINT IF EXISTS accounts_pkey;\nALTER TABLE "stripe"."accounts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."accounts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."accounts" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 3: Convert _id to generated column id for all Stripe entity tables\n-- ============================================================================\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS active_entitlements_pkey;\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD PRIMARY KEY (id);\n\n-- charges\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS charges_pkey;\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."charges" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."charges" ADD PRIMARY KEY (id);\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS checkout_session_line_items_pkey;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD PRIMARY KEY (id);\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS checkout_sessions_pkey;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD PRIMARY KEY (id);\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS credit_notes_pkey;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."credit_notes" ADD PRIMARY KEY (id);\n\n-- coupons\nALTER TABLE "stripe"."coupons" DROP CONSTRAINT IF EXISTS coupons_pkey;\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."coupons" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."coupons" ADD PRIMARY KEY (id);\n\n-- customers\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS customers_pkey;\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."customers" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."customers" ADD PRIMARY KEY (id);\n\n-- disputes\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS disputes_pkey;\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."disputes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."disputes" ADD PRIMARY KEY (id);\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS early_fraud_warnings_pkey;\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD PRIMARY KEY (id);\n\n-- events\nALTER TABLE "stripe"."events" DROP CONSTRAINT IF EXISTS events_pkey;\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."events" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."events" ADD PRIMARY KEY (id);\n\n-- features\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS features_pkey;\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."features" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."features" ADD PRIMARY KEY (id);\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS invoices_pkey;\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."invoices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."invoices" ADD PRIMARY KEY (id);\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS payment_intents_pkey;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_intents" ADD PRIMARY KEY (id);\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS payment_methods_pkey;\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_methods" ADD PRIMARY KEY (id);\n\n-- payouts\nALTER TABLE "stripe"."payouts" DROP CONSTRAINT IF EXISTS payouts_pkey;\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payouts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payouts" ADD PRIMARY KEY (id);\n\n-- plans\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS plans_pkey;\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."plans" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."plans" ADD PRIMARY KEY (id);\n\n-- prices\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS prices_pkey;\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."prices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."prices" ADD PRIMARY KEY (id);\n\n-- products\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS products_pkey;\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."products" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."products" ADD PRIMARY KEY (id);\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS refunds_pkey;\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."refunds" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."refunds" ADD PRIMARY KEY (id);\n\n-- reviews\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS reviews_pkey;\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."reviews" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."reviews" ADD PRIMARY KEY (id);\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS setup_intents_pkey;\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."setup_intents" ADD PRIMARY KEY (id);\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS subscription_items_pkey;\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_items" ADD PRIMARY KEY (id);\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS subscription_schedules_pkey;\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD PRIMARY KEY (id);\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS subscriptions_pkey;\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscriptions" ADD PRIMARY KEY (id);\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS tax_ids_pkey;\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."tax_ids" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 4: Handle metadata tables\n-- ============================================================================\n\n-- _managed_webhooks (internal metadata table, doesn\'t use _raw_data pattern)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- _sync_status (internal table, uses auto-incrementing id not from Stripe)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- ============================================================================\n-- Step 5: Recreate all foreign key constraints\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\n', + }, + { + name: '0051_remove_webhook_uuid.sql', + sql: '-- Remove UUID from managed webhooks\n-- UUID-based routing is no longer used; webhooks are identified by exact URL match\n-- Legacy webhooks with UUID in URL will be automatically deleted and recreated\n\ndrop index if exists "stripe"."stripe_managed_webhooks_uuid_idx";\n\nalter table "stripe"."_managed_webhooks" drop column if exists "uuid";\n', + }, + { + name: '0052_webhook_url_uniqueness.sql', + sql: '-- Add unique constraint on URL per account to prevent duplicate webhooks at database level\n-- This prevents race conditions where multiple instances try to create webhooks for the same URL\n-- Since UUIDs have been removed from URLs, we can enforce strict uniqueness on the URL column per account\n-- Note: Different accounts can have webhooks with the same URL\n\nalter table "stripe"."_managed_webhooks"\n add constraint managed_webhooks_url_account_unique unique ("url", "account_id");\n', + }, + { + name: '0053_sync_observability.sql', + sql: '-- Observable Sync System: Track sync runs and individual object syncs\n-- Enables observability for long-running syncs (days, not minutes)\n--\n-- Two-level hierarchy:\n-- _sync_run: Parent sync operation (one active per account)\n-- _sync_obj_run: Individual object syncs within a run\n--\n-- Features:\n-- - Only one active run per account (EXCLUDE constraint)\n-- - Configurable object concurrency (max_concurrent)\n-- - Stale detection (is_stale in dashboard view)\n-- - Progress tracking per object\n\n-- Step 1: Create _sync_run table (parent sync operation)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_run" (\n "_account_id" TEXT NOT NULL,\n started_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n status TEXT NOT NULL DEFAULT \'running\' CHECK (status IN (\'running\', \'complete\', \'error\')),\n max_concurrent INTEGER NOT NULL DEFAULT 3,\n completed_at TIMESTAMPTZ,\n error_message TEXT,\n triggered_by TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", started_at),\n\n -- Only one active run per account\n CONSTRAINT one_active_run_per_account\n EXCLUDE ("_account_id" WITH =) WHERE (status = \'running\'),\n\n -- Foreign key to accounts table\n CONSTRAINT fk_sync_run_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)\n);\n\n-- Step 2: Add updated_at trigger for _sync_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 3: Create _sync_obj_run table (individual object syncs)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_run" (\n "_account_id" TEXT NOT NULL,\n run_started_at TIMESTAMPTZ NOT NULL,\n object TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT \'pending\' CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n processed_count INTEGER DEFAULT 0,\n cursor TEXT,\n error_message TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", run_started_at, object),\n\n -- Foreign key to parent sync run\n CONSTRAINT fk_sync_obj_run_parent\n FOREIGN KEY ("_account_id", run_started_at) REFERENCES "stripe"."_sync_run" ("_account_id", started_at)\n);\n\n-- Step 4: Add updated_at trigger for _sync_obj_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_obj_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 5: Create indexes for efficient queries\nCREATE INDEX IF NOT EXISTS idx_sync_run_account_status\n ON "stripe"."_sync_run" ("_account_id", status);\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_run_status\n ON "stripe"."_sync_obj_run" ("_account_id", run_started_at, status);\n\n-- Step 6: Create sync_dashboard view for observability\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n r."_account_id" as account_id,\n r.started_at as run_started_at,\n r.status as run_status,\n r.completed_at as run_completed_at,\n r.max_concurrent,\n r.triggered_by,\n o.object,\n o.status as object_status,\n o.started_at as object_started_at,\n o.completed_at as object_completed_at,\n o.processed_count,\n o.error_message,\n o.updated_at,\n -- Duration in seconds\n EXTRACT(EPOCH FROM (COALESCE(o.completed_at, now()) - o.started_at))::integer as duration_seconds,\n -- Stale detection: running but no update in 5 min\n CASE\n WHEN o.status = \'running\' AND o.updated_at < now() - interval \'5 minutes\'\n THEN true\n ELSE false\n END as is_stale\nFROM "stripe"."_sync_run" r\nLEFT JOIN "stripe"."_sync_obj_run" o\n ON o."_account_id" = r."_account_id"\n AND o.run_started_at = r.started_at;\n', + }, + { + name: '0054_drop_sync_status.sql', + sql: '-- Drop the old _sync_status table\n-- This table has been replaced by _sync_run and _sync_obj_run for better observability\n-- See migration 0053_sync_observability.sql\n\nDROP TABLE IF EXISTS "stripe"."_sync_status";\n', + }, + { + name: '0055_bigint_money_columns.sql', + sql: '-- Fix generated columns: must drop and recreate with ::bigint cast\n-- Money columns that can overflow PostgreSQL integer max (~2.1 billion)\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::bigint) STORED;\n\n-- customers\nALTER TABLE "stripe"."customers" DROP COLUMN "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'balance\')::bigint) STORED;\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP COLUMN "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'tax\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::bigint) STORED;\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\n\n-- prices\nALTER TABLE "stripe"."prices" DROP COLUMN "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::bigint) STORED;\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP COLUMN "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\n', + }, + { + name: '0056_sync_run_closed_at.sql', + sql: '-- Add closed_at column to _sync_run\n-- closed_at IS NULL means the run is still active\n-- Status is derived from object states when closed_at IS NOT NULL\n\n-- Step 1: Drop dependent view first\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Drop the old constraint, status column, and completed_at column\nALTER TABLE "stripe"."_sync_run" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS status;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS completed_at;\n\n-- Step 3: Add closed_at column\nALTER TABLE "stripe"."_sync_run" ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;\n\n-- Step 4: Create exclusion constraint (only one active run per account)\nALTER TABLE "stripe"."_sync_run"\nADD CONSTRAINT one_active_run_per_account\nEXCLUDE ("_account_id" WITH =) WHERE (closed_at IS NULL);\n\n-- Step 5: Recreate sync_dashboard view (run-level only, one row per run)\n-- Base table: _sync_run (parent sync operation)\n-- Child table: _sync_obj_run (individual object syncs)\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n run."_account_id" as account_id,\n run.started_at,\n run.closed_at,\n run.max_concurrent,\n run.triggered_by,\n run.updated_at,\n -- Derived status from object states\n CASE\n WHEN run.closed_at IS NULL THEN \'running\'\n WHEN EXISTS (\n SELECT 1 FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ) THEN \'error\'\n ELSE \'complete\'\n END as status,\n -- First error message from failed objects\n (SELECT obj.error_message FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ORDER BY obj.object LIMIT 1) as error_message,\n -- Total processed count across all objects\n COALESCE((SELECT SUM(obj.processed_count) FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at), 0) as processed_count\nFROM "stripe"."_sync_run" run;\n', + }, + { + name: '0057_rename_sync_tables.sql', + sql: '-- Rename sync observability tables and create public sync_runs view\n-- Internal tables use _ prefix, public view is sync_runs\n\n-- Step 1: Drop the old sync_dashboard view\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Rename tables to plural (keep _ prefix for internal tables)\nALTER TABLE "stripe"."_sync_run" RENAME TO "_sync_runs";\nALTER TABLE "stripe"."_sync_obj_run" RENAME TO "_sync_obj_runs";\n\n-- Step 3: Update foreign key constraint name\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS fk_sync_obj_run_parent;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT fk_sync_obj_runs_parent\n FOREIGN KEY ("_account_id", run_started_at)\n REFERENCES "stripe"."_sync_runs" ("_account_id", started_at);\n\n-- Step 4: Recreate indexes with new table names\nDROP INDEX IF EXISTS "stripe"."idx_sync_run_account_status";\nDROP INDEX IF EXISTS "stripe"."idx_sync_obj_run_status";\n\nCREATE INDEX idx_sync_runs_account_status\n ON "stripe"."_sync_runs" ("_account_id", closed_at);\n\nCREATE INDEX idx_sync_obj_runs_status\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status);\n\n-- Step 5: Create public sync_runs view (one row per run with aggregates)\nCREATE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n', + }, + { + name: '0058_improve_sync_runs_status.sql', + sql: "-- Improve sync_runs view status logic\n-- More granular status based on actual object run states\n\nDROP VIEW IF EXISTS \"stripe\".\"sync_runs\";\n\nCREATE VIEW \"stripe\".\"sync_runs\" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count,\n COUNT(*) FILTER (WHERE o.status = 'error') as error_count,\n COUNT(*) FILTER (WHERE o.status = 'running') as running_count,\n COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n -- Run still open (closed_at IS NULL)\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending'\n WHEN r.closed_at IS NULL THEN 'running'\n -- Run closed (closed_at IS NOT NULL)\n WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error'\n ELSE 'complete'\n END as status\nFROM \"stripe\".\"_sync_runs\" r\nLEFT JOIN \"stripe\".\"_sync_obj_runs\" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n", + }, + { + name: '0059_sigma_subscription_item_change_events_v2_beta.sql', + sql: '-- event_timestamp and event_type are not generated columns because they are not immutable. \n-- Postgres requires generated expressions to be immutable.\n\nCREATE TABLE IF NOT EXISTS "stripe"."subscription_item_change_events_v2_beta" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "event_timestamp" timestamptz NOT NULL,\n "event_type" text NOT NULL,\n "subscription_item_id" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "event_timestamp", "event_type", "subscription_item_id")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n DROP CONSTRAINT IF EXISTS fk_subscription_item_change_events_v2_beta_account;\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD CONSTRAINT fk_subscription_item_change_events_v2_beta_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."subscription_item_change_events_v2_beta";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."subscription_item_change_events_v2_beta"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "currency" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'currency\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "mrr_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'mrr_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "quantity_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'quantity_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "subscription_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'subscription_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "customer_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'customer_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "price_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'price_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "product_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'product_id\', \'\'))::text) STORED;\n\n-- Keep as text to avoid non-immutable timestamp casts in a generated column\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "local_event_timestamp" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'local_event_timestamp\', \'\'))::text) STORED;', + }, + { + name: '0060_sigma_exchange_rates_from_usd.sql', + sql: '\nCREATE TABLE IF NOT EXISTS "stripe"."exchange_rates_from_usd" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "date" date NOT NULL,\n "sell_currency" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "date", "sell_currency")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."exchange_rates_from_usd"\n DROP CONSTRAINT IF EXISTS fk_exchange_rates_from_usd_account;\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD CONSTRAINT fk_exchange_rates_from_usd_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."exchange_rates_from_usd";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."exchange_rates_from_usd"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD COLUMN IF NOT EXISTS "buy_currency_exchange_rates" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'buy_currency_exchange_rates\', \'\'))::text) STORED;\n\n-- Index on date for efficient range queries\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_date\n ON "stripe"."exchange_rates_from_usd" ("date");\n\n-- Index on sell_currency for filtering by currency\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_sell_currency\n ON "stripe"."exchange_rates_from_usd" ("sell_currency");\n\n', + }, + { + name: '0061_add_page_cursor.sql', + sql: '-- Add page_cursor column for pagination state within a single sync run.\n-- This is used to store the starting_after ID for backfills using Stripe list calls.\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS page_cursor text;\n', + }, + { + name: '0062_sigma_query_runs.sql', + sql: '-- Allow parallel sync runs per triggered_by (sigma-worker vs stripe-worker)\nALTER TABLE "stripe"."_sync_runs" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\nADD CONSTRAINT one_active_run_per_account_triggered_by\nEXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n) WHERE (closed_at IS NULL);\n', + }, + { + name: '0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql', + sql: '-- Drop unused sigma beta tables if they exist.\nDROP TABLE IF EXISTS "stripe"."subscription_item_change_events_v2_beta";\nDROP TABLE IF EXISTS "stripe"."exchange_rates_from_usd";\n', + }, + { + name: '0064_add_created_gte_lte.sql', + sql: '-- Add created_gte / created_lte columns for time-range partitioned parallel sync.\n-- Workers use these to scope their Stripe list calls to a specific created window.\n-- Stored as Unix epoch seconds (INTEGER) to match Stripe\'s created filter format.\n-- created_gte defaults to 0 for non-chunked rows (required by PK).\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_gte INTEGER NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_lte INTEGER;\n\n-- Expand PK to include created_gte so multiple time-range chunks of the same object can coexist.\n-- PK constraint kept original name from 0053 (_sync_obj_run_pkey) after table rename in 0057.\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_run_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte);\n', + }, + { + name: '0065_add_created_lte_to_pk.sql', + sql: '-- Include created_lte in the PK so chunks with the same created_gte but\n-- different created_lte can coexist. Requires a NOT NULL default first.\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET DEFAULT 0;\n\nUPDATE "stripe"."_sync_obj_runs"\n SET created_lte = 0\n WHERE created_lte IS NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET NOT NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte, created_lte);\n', + }, + { + name: '0066_rate_limits.sql', + sql: '-- Rate limiting table and function for cross-process request throttling.\n-- Used by claimNextTask to cap how many claims/sec hit the database.\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n', + }, + { + name: '0067_add_priority_to_sync_obj_runs.sql', + sql: '-- Add priority column to _sync_obj_runs for deterministic task ordering.\n-- Priority mirrors the `order` field from resourceRegistry so workers\n-- always process parent resources (products, prices) before children\n-- (subscriptions, invoices).\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_runs_priority\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status, priority);\n', + }, + { + name: '0068_sync_obj_progress_view.sql', + sql: '-- Per-object sync progress view for monitoring.\n-- Defaults to the newest run per account; callers can filter by a specific\n-- run_started_at if needed.\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\n\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', + }, + { + name: '0069_internal_sync_schema.sql', + sql: '-- Internal sync metadata schema bootstrap for OpenAPI runtime.\n-- Uses idempotent DDL so it can be safely re-run.\n\nCREATE EXTENSION IF NOT EXISTS btree_gist;\n\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW._updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW.updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n "_raw_data" jsonb NOT NULL,\n "id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED,\n "api_key_hashes" text[] NOT NULL DEFAULT \'{}\',\n "first_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_last_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("id")\n);\nCREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes"\n ON "stripe"."accounts" USING GIN ("api_key_hashes");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."accounts"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" (\n "id" text PRIMARY KEY,\n "object" text,\n "url" text NOT NULL,\n "enabled_events" jsonb NOT NULL,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text NOT NULL,\n "status" text,\n "api_version" text,\n "created" bigint,\n "last_synced_at" timestamptz,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n "account_id" text NOT NULL\n);\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id");\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "fk_managed_webhooks_account"\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status"\n ON "stripe"."_managed_webhooks" ("status");\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled"\n ON "stripe"."_managed_webhooks" ("enabled");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_managed_webhooks"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" (\n "_account_id" text NOT NULL,\n "started_at" timestamptz NOT NULL DEFAULT now(),\n "closed_at" timestamptz,\n "max_concurrent" integer NOT NULL DEFAULT 3,\n "triggered_by" text,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "started_at")\n);\nALTER TABLE "stripe"."_sync_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_runs_account";\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT "fk_sync_runs_account"\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by;\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT one_active_run_per_account_triggered_by\n EXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n ) WHERE (closed_at IS NULL);\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status"\n ON "stripe"."_sync_runs" ("_account_id", "closed_at");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" (\n "_account_id" text NOT NULL,\n "run_started_at" timestamptz NOT NULL,\n "object" text NOT NULL,\n "status" text NOT NULL DEFAULT \'pending\'\n CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n "started_at" timestamptz,\n "completed_at" timestamptz,\n "processed_count" integer NOT NULL DEFAULT 0,\n "cursor" text,\n "page_cursor" text,\n "created_gte" integer NOT NULL DEFAULT 0,\n "created_lte" integer NOT NULL DEFAULT 0,\n "priority" integer NOT NULL DEFAULT 0,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte")\n);\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "page_cursor" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "fk_sync_obj_runs_parent"\n FOREIGN KEY ("_account_id", "run_started_at")\n REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_obj_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status");\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE OR REPLACE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n CASE\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = \'running\') > 0 THEN \'running\'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = \'pending\')) THEN \'pending\'\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', + }, +] diff --git a/packages/sync-engine/src/index.ts b/packages/sync-engine/src/index.ts index 362c427c..96fe5cba 100644 --- a/packages/sync-engine/src/index.ts +++ b/packages/sync-engine/src/index.ts @@ -9,7 +9,9 @@ export { getTableName } from './resourceRegistry' export type * from './types' export { PostgresClient } from './database/postgres' -export { runMigrations } from './database/migrate' +export { runMigrations, runMigrationsFromContent } from './database/migrate' +export { embeddedMigrations } from './database/migrations-embedded' +export type { EmbeddedMigration } from './database/migrations-embedded' export { hashApiKey } from './utils/hashApiKey' export { createStripeWebSocketClient } from './websocket-client' export type { diff --git a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts index 583982ce..0868877f 100644 --- a/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts +++ b/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts @@ -1,4 +1,4 @@ -import { StripeSync, runMigrations, VERSION } from '../../index' +import { StripeSync, runMigrationsFromContent, VERSION, embeddedMigrations } from '../../index' import postgres from 'postgres' // Get management API base URL from environment variable (for testing against localhost/staging) @@ -386,13 +386,16 @@ Deno.serve(async (req) => { const enableSigma = (Deno.env.get('ENABLE_SIGMA') ?? 'false') === 'true' const schemaName = Deno.env.get('SYNC_SCHEMA_NAME') ?? 'stripe' const syncTablesSchemaName = Deno.env.get('SYNC_TABLES_SCHEMA_NAME') ?? schemaName - await runMigrations({ - databaseUrl: dbUrl, - enableSigma, - stripeApiVersion: Deno.env.get('STRIPE_API_VERSION') ?? '2020-08-27', - schemaName, - syncTablesSchemaName, - }) + await runMigrationsFromContent( + { + databaseUrl: dbUrl, + enableSigma, + stripeApiVersion: Deno.env.get('STRIPE_API_VERSION') ?? '2020-08-27', + schemaName, + syncTablesSchemaName, + }, + embeddedMigrations + ) stripeSync = await StripeSync.create({ poolConfig: { connectionString: dbUrl, max: 2 }, // Need 2 for advisory lock + queries From 27e4b3856cdf96535d08719ed7df4a537be0f35f Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:57:24 -0500 Subject: [PATCH 08/11] update --- packages/fastify-app/package.json | 2 +- .../sync-engine/src/database/migrations-embedded.ts | 2 +- .../database/migrations/0069_internal_sync_schema.sql | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/fastify-app/package.json b/packages/fastify-app/package.json index 1504884d..281780ee 100644 --- a/packages/fastify-app/package.json +++ b/packages/fastify-app/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "lint": "eslint src --ext .ts", "start": "NODE_ENV=production node dist/src/server.js", - "test": "DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@localhost:55432/postgres} TEST_POSTGRES_DB_URL=${TEST_POSTGRES_DB_URL:-postgresql://postgres:postgres@localhost:55432/postgres} STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY:-sk_test_123} STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET:-whsec_test} vitest" + "test": "vitest" }, "author": "Supabase", "license": "MIT", diff --git a/packages/sync-engine/src/database/migrations-embedded.ts b/packages/sync-engine/src/database/migrations-embedded.ts index 07076bef..d14be52c 100644 --- a/packages/sync-engine/src/database/migrations-embedded.ts +++ b/packages/sync-engine/src/database/migrations-embedded.ts @@ -285,6 +285,6 @@ export const embeddedMigrations: EmbeddedMigration[] = [ }, { name: '0069_internal_sync_schema.sql', - sql: '-- Internal sync metadata schema bootstrap for OpenAPI runtime.\n-- Uses idempotent DDL so it can be safely re-run.\n\nCREATE EXTENSION IF NOT EXISTS btree_gist;\n\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW._updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW.updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n "_raw_data" jsonb NOT NULL,\n "id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED,\n "api_key_hashes" text[] NOT NULL DEFAULT \'{}\',\n "first_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_last_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("id")\n);\nCREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes"\n ON "stripe"."accounts" USING GIN ("api_key_hashes");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."accounts"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" (\n "id" text PRIMARY KEY,\n "object" text,\n "url" text NOT NULL,\n "enabled_events" jsonb NOT NULL,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text NOT NULL,\n "status" text,\n "api_version" text,\n "created" bigint,\n "last_synced_at" timestamptz,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n "account_id" text NOT NULL\n);\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id");\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "fk_managed_webhooks_account"\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status"\n ON "stripe"."_managed_webhooks" ("status");\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled"\n ON "stripe"."_managed_webhooks" ("enabled");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_managed_webhooks"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" (\n "_account_id" text NOT NULL,\n "started_at" timestamptz NOT NULL DEFAULT now(),\n "closed_at" timestamptz,\n "max_concurrent" integer NOT NULL DEFAULT 3,\n "triggered_by" text,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "started_at")\n);\nALTER TABLE "stripe"."_sync_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_runs_account";\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT "fk_sync_runs_account"\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by;\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT one_active_run_per_account_triggered_by\n EXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n ) WHERE (closed_at IS NULL);\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status"\n ON "stripe"."_sync_runs" ("_account_id", "closed_at");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" (\n "_account_id" text NOT NULL,\n "run_started_at" timestamptz NOT NULL,\n "object" text NOT NULL,\n "status" text NOT NULL DEFAULT \'pending\'\n CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n "started_at" timestamptz,\n "completed_at" timestamptz,\n "processed_count" integer NOT NULL DEFAULT 0,\n "cursor" text,\n "page_cursor" text,\n "created_gte" integer NOT NULL DEFAULT 0,\n "created_lte" integer NOT NULL DEFAULT 0,\n "priority" integer NOT NULL DEFAULT 0,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte")\n);\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "page_cursor" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "fk_sync_obj_runs_parent"\n FOREIGN KEY ("_account_id", "run_started_at")\n REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_obj_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status");\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE OR REPLACE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n CASE\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = \'running\') > 0 THEN \'running\'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = \'pending\')) THEN \'pending\'\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', + sql: '-- Internal sync metadata schema bootstrap for OpenAPI runtime.\n-- Uses idempotent DDL so it can be safely re-run.\n\nCREATE EXTENSION IF NOT EXISTS btree_gist;\n\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n -- Support both legacy "updated_at" and newer "_updated_at" columns.\n -- jsonb_populate_record silently ignores keys that are not present on NEW.\n NEW := jsonb_populate_record(\n NEW,\n jsonb_build_object(\n \'updated_at\', now(),\n \'_updated_at\', now()\n )\n );\n RETURN NEW;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW.updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n "_raw_data" jsonb NOT NULL,\n "id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED,\n "api_key_hashes" text[] NOT NULL DEFAULT \'{}\',\n "first_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_last_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("id")\n);\nCREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes"\n ON "stripe"."accounts" USING GIN ("api_key_hashes");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."accounts"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" (\n "id" text PRIMARY KEY,\n "object" text,\n "url" text NOT NULL,\n "enabled_events" jsonb NOT NULL,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text NOT NULL,\n "status" text,\n "api_version" text,\n "created" bigint,\n "last_synced_at" timestamptz,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n "account_id" text NOT NULL\n);\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id");\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "fk_managed_webhooks_account"\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status"\n ON "stripe"."_managed_webhooks" ("status");\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled"\n ON "stripe"."_managed_webhooks" ("enabled");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_managed_webhooks"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" (\n "_account_id" text NOT NULL,\n "started_at" timestamptz NOT NULL DEFAULT now(),\n "closed_at" timestamptz,\n "max_concurrent" integer NOT NULL DEFAULT 3,\n "triggered_by" text,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "started_at")\n);\nALTER TABLE "stripe"."_sync_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_runs_account";\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT "fk_sync_runs_account"\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by;\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT one_active_run_per_account_triggered_by\n EXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n ) WHERE (closed_at IS NULL);\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status"\n ON "stripe"."_sync_runs" ("_account_id", "closed_at");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" (\n "_account_id" text NOT NULL,\n "run_started_at" timestamptz NOT NULL,\n "object" text NOT NULL,\n "status" text NOT NULL DEFAULT \'pending\'\n CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n "started_at" timestamptz,\n "completed_at" timestamptz,\n "processed_count" integer NOT NULL DEFAULT 0,\n "cursor" text,\n "page_cursor" text,\n "created_gte" integer NOT NULL DEFAULT 0,\n "created_lte" integer NOT NULL DEFAULT 0,\n "priority" integer NOT NULL DEFAULT 0,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte")\n);\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "page_cursor" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "fk_sync_obj_runs_parent"\n FOREIGN KEY ("_account_id", "run_started_at")\n REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_obj_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status");\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE OR REPLACE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n CASE\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = \'running\') > 0 THEN \'running\'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = \'pending\')) THEN \'pending\'\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', }, ] diff --git a/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql b/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql index 90015f35..7efd2d50 100644 --- a/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql +++ b/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql @@ -7,7 +7,15 @@ CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN - NEW._updated_at = now(); + -- Support both legacy "updated_at" and newer "_updated_at" columns. + -- jsonb_populate_record silently ignores keys that are not present on NEW. + NEW := jsonb_populate_record( + NEW, + jsonb_build_object( + 'updated_at', now(), + '_updated_at', now() + ) + ); RETURN NEW; END; $$; From 84a327bdd44c3d5a0fe7f200b812cdcb465cc731 Mon Sep 17 00:00:00 2001 From: Kunwarvir Dhillon <243457111+kdhillon-stripe@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:30:51 -0500 Subject: [PATCH 09/11] init --- .../src/tests/integration/postgres-sync-observability.test.ts | 2 +- .../src/tests/integration/stripeSync-integration.test.ts | 2 +- .../src/tests/integration/stripeSync-sigma-integration.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sync-engine/src/tests/integration/postgres-sync-observability.test.ts b/packages/sync-engine/src/tests/integration/postgres-sync-observability.test.ts index ec7af99e..1338c8a1 100644 --- a/packages/sync-engine/src/tests/integration/postgres-sync-observability.test.ts +++ b/packages/sync-engine/src/tests/integration/postgres-sync-observability.test.ts @@ -21,7 +21,7 @@ describe('Observable Sync System Methods', () => { { id: testAccountId, raw_data: { id: testAccountId, object: 'account' } }, `test_api_key_hash_${testAccountId}` ) - }, 30_000) + }) afterAll(async () => { if (postgresClient) await postgresClient.pool.end() diff --git a/packages/sync-engine/src/tests/integration/stripeSync-integration.test.ts b/packages/sync-engine/src/tests/integration/stripeSync-integration.test.ts index fc638cf1..fcd0403a 100644 --- a/packages/sync-engine/src/tests/integration/stripeSync-integration.test.ts +++ b/packages/sync-engine/src/tests/integration/stripeSync-integration.test.ts @@ -23,7 +23,7 @@ describe('StripeSync Integration Tests', () => { beforeAll(async () => { db = await setupTestDatabase() validator = new DatabaseValidator(db.databaseUrl) - }, 30_000) + }) afterAll(async () => { if (validator) await validator.close() diff --git a/packages/sync-engine/src/tests/integration/stripeSync-sigma-integration.test.ts b/packages/sync-engine/src/tests/integration/stripeSync-sigma-integration.test.ts index 62bfb8b8..c13a2326 100644 --- a/packages/sync-engine/src/tests/integration/stripeSync-sigma-integration.test.ts +++ b/packages/sync-engine/src/tests/integration/stripeSync-sigma-integration.test.ts @@ -129,7 +129,7 @@ describe('StripeSync Sigma Integration Tests', () => { beforeAll(async () => { db = await setupTestDatabase({ enableSigma: true }) validator = new DatabaseValidator(db.databaseUrl) - }, 30_000) + }) afterAll(async () => { if (validator) await validator.close() From cd381f825036bbb8210a89ab1697239f748bca18 Mon Sep 17 00:00:00 2001 From: Yostra Date: Fri, 6 Mar 2026 03:18:39 +0100 Subject: [PATCH 10/11] use node:buffer --- packages/sync-engine/src/openapi/postgresAdapter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sync-engine/src/openapi/postgresAdapter.ts b/packages/sync-engine/src/openapi/postgresAdapter.ts index 503d2ffe..0f782df9 100644 --- a/packages/sync-engine/src/openapi/postgresAdapter.ts +++ b/packages/sync-engine/src/openapi/postgresAdapter.ts @@ -1,3 +1,4 @@ +import { Buffer } from 'node:buffer' import { createHash } from 'node:crypto' import type { DialectAdapter } from './dialectAdapter' import type { ParsedColumn, ParsedResourceTable, ScalarType } from './types' From b3dfe0dcf5f4bab29179560631c847280499f4be Mon Sep 17 00:00:00 2001 From: Yostra Date: Fri, 6 Mar 2026 17:33:14 +0100 Subject: [PATCH 11/11] start clean --- packages/sync-engine/src/database/migrate.ts | 50 +- .../src/database/migrations-embedded.ts | 276 --- .../migrations/0000_initial_migration.sql | 246 ++- .../src/database/migrations/0001_products.sql | 17 - .../database/migrations/0002_customers.sql | 23 - .../src/database/migrations/0003_prices.sql | 34 - .../migrations/0004_subscriptions.sql | 56 - .../src/database/migrations/0005_invoices.sql | 77 - .../src/database/migrations/0006_charges.sql | 43 - .../src/database/migrations/0007_coupons.sql | 19 - .../src/database/migrations/0008_disputes.sql | 17 - .../src/database/migrations/0009_events.sql | 12 - .../src/database/migrations/0010_payouts.sql | 30 - .../src/database/migrations/0011_plans.sql | 25 - .../migrations/0012_add_updated_at.sql | 108 - .../0013_add_subscription_items.sql | 12 - .../0014_migrate_subscription_items.sql | 26 - .../migrations/0015_add_customer_deleted.sql | 2 - .../migrations/0016_add_invoice_indexes.sql | 2 - .../0017_drop_charges_unavailable_columns.sql | 6 - .../migrations/0018_setup_intents.sql | 17 - .../migrations/0019_payment_methods.sql | 12 - ...20_disputes_payment_intent_created_idx.sql | 3 - .../migrations/0021_payment_intent.sql | 42 - .../database/migrations/0022_adjust_plans.sql | 5 - .../migrations/0023_invoice_deleted.sql | 1 - .../0024_subscription_schedules.sql | 29 - .../src/database/migrations/0025_tax_ids.sql | 14 - .../database/migrations/0026_credit_notes.sql | 36 - ...027_add_marketing_features_to_products.sql | 2 - .../migrations/0028_early_fraud_warning.sql | 22 - .../src/database/migrations/0029_reviews.sql | 28 - .../src/database/migrations/0030_refunds.sql | 29 - .../migrations/0031_add_default_price.sql | 2 - .../0032_update_subscription_items.sql | 3 - .../migrations/0033_add_last_synced_at.sql | 85 - .../migrations/0034_remove_foreign_keys.sql | 13 - .../migrations/0035_checkout_sessions.sql | 77 - .../0036_checkout_session_line_items.sql | 24 - .../database/migrations/0037_add_features.sql | 18 - .../migrations/0038_active_entitlement.sql | 20 - ...0039_add_paused_to_subscription_status.sql | 1 - .../migrations/0040_managed_webhooks.sql | 28 - .../0041_rename_managed_webhooks.sql | 2 - ...042_convert_to_jsonb_generated_columns.sql | 1821 ----------------- .../migrations/0043_add_account_id.sql | 49 - .../0044_make_account_id_required.sql | 54 - .../database/migrations/0045_sync_status.sql | 18 - .../0046_sync_status_per_account.sql | 91 - .../migrations/0047_api_key_hashes.sql | 12 - .../0048_rename_reserved_columns.sql | 1253 ------------ ...ndant_underscores_from_metadata_tables.sql | 68 - .../0050_rename_id_to_match_stripe_api.sql | 239 --- .../migrations/0051_remove_webhook_uuid.sql | 7 - .../0052_webhook_url_uniqueness.sql | 7 - .../migrations/0053_sync_observability.sql | 104 - .../migrations/0054_drop_sync_status.sql | 5 - .../migrations/0055_bigint_money_columns.sql | 72 - .../migrations/0056_sync_run_closed_at.sql | 53 - .../migrations/0057_rename_sync_tables.sql | 57 - .../0058_improve_sync_runs_status.sql | 36 - ...ubscription_item_change_events_v2_beta.sql | 61 - .../0060_sigma_exchange_rates_from_usd.sql | 38 - .../migrations/0061_add_page_cursor.sql | 3 - .../migrations/0062_sigma_query_runs.sql | 8 - ...cription_item_and_exchange_rate_tables.sql | 3 - .../migrations/0064_add_created_gte_lte.sql | 13 - .../migrations/0065_add_created_lte_to_pk.sql | 15 - .../database/migrations/0066_rate_limits.sql | 43 - .../0067_add_priority_to_sync_obj_runs.sql | 10 - .../0068_sync_obj_progress_view.sql | 23 - .../migrations/0069_internal_sync_schema.sql | 245 --- 72 files changed, 285 insertions(+), 5717 deletions(-) delete mode 100644 packages/sync-engine/src/database/migrations/0001_products.sql delete mode 100644 packages/sync-engine/src/database/migrations/0002_customers.sql delete mode 100644 packages/sync-engine/src/database/migrations/0003_prices.sql delete mode 100644 packages/sync-engine/src/database/migrations/0004_subscriptions.sql delete mode 100644 packages/sync-engine/src/database/migrations/0005_invoices.sql delete mode 100644 packages/sync-engine/src/database/migrations/0006_charges.sql delete mode 100644 packages/sync-engine/src/database/migrations/0007_coupons.sql delete mode 100644 packages/sync-engine/src/database/migrations/0008_disputes.sql delete mode 100644 packages/sync-engine/src/database/migrations/0009_events.sql delete mode 100644 packages/sync-engine/src/database/migrations/0010_payouts.sql delete mode 100644 packages/sync-engine/src/database/migrations/0011_plans.sql delete mode 100644 packages/sync-engine/src/database/migrations/0012_add_updated_at.sql delete mode 100644 packages/sync-engine/src/database/migrations/0013_add_subscription_items.sql delete mode 100644 packages/sync-engine/src/database/migrations/0014_migrate_subscription_items.sql delete mode 100644 packages/sync-engine/src/database/migrations/0015_add_customer_deleted.sql delete mode 100644 packages/sync-engine/src/database/migrations/0016_add_invoice_indexes.sql delete mode 100644 packages/sync-engine/src/database/migrations/0017_drop_charges_unavailable_columns.sql delete mode 100644 packages/sync-engine/src/database/migrations/0018_setup_intents.sql delete mode 100644 packages/sync-engine/src/database/migrations/0019_payment_methods.sql delete mode 100644 packages/sync-engine/src/database/migrations/0020_disputes_payment_intent_created_idx.sql delete mode 100644 packages/sync-engine/src/database/migrations/0021_payment_intent.sql delete mode 100644 packages/sync-engine/src/database/migrations/0022_adjust_plans.sql delete mode 100644 packages/sync-engine/src/database/migrations/0023_invoice_deleted.sql delete mode 100644 packages/sync-engine/src/database/migrations/0024_subscription_schedules.sql delete mode 100644 packages/sync-engine/src/database/migrations/0025_tax_ids.sql delete mode 100644 packages/sync-engine/src/database/migrations/0026_credit_notes.sql delete mode 100644 packages/sync-engine/src/database/migrations/0027_add_marketing_features_to_products.sql delete mode 100644 packages/sync-engine/src/database/migrations/0028_early_fraud_warning.sql delete mode 100644 packages/sync-engine/src/database/migrations/0029_reviews.sql delete mode 100644 packages/sync-engine/src/database/migrations/0030_refunds.sql delete mode 100644 packages/sync-engine/src/database/migrations/0031_add_default_price.sql delete mode 100644 packages/sync-engine/src/database/migrations/0032_update_subscription_items.sql delete mode 100644 packages/sync-engine/src/database/migrations/0033_add_last_synced_at.sql delete mode 100644 packages/sync-engine/src/database/migrations/0034_remove_foreign_keys.sql delete mode 100644 packages/sync-engine/src/database/migrations/0035_checkout_sessions.sql delete mode 100644 packages/sync-engine/src/database/migrations/0036_checkout_session_line_items.sql delete mode 100644 packages/sync-engine/src/database/migrations/0037_add_features.sql delete mode 100644 packages/sync-engine/src/database/migrations/0038_active_entitlement.sql delete mode 100644 packages/sync-engine/src/database/migrations/0039_add_paused_to_subscription_status.sql delete mode 100644 packages/sync-engine/src/database/migrations/0040_managed_webhooks.sql delete mode 100644 packages/sync-engine/src/database/migrations/0041_rename_managed_webhooks.sql delete mode 100644 packages/sync-engine/src/database/migrations/0042_convert_to_jsonb_generated_columns.sql delete mode 100644 packages/sync-engine/src/database/migrations/0043_add_account_id.sql delete mode 100644 packages/sync-engine/src/database/migrations/0044_make_account_id_required.sql delete mode 100644 packages/sync-engine/src/database/migrations/0045_sync_status.sql delete mode 100644 packages/sync-engine/src/database/migrations/0046_sync_status_per_account.sql delete mode 100644 packages/sync-engine/src/database/migrations/0047_api_key_hashes.sql delete mode 100644 packages/sync-engine/src/database/migrations/0048_rename_reserved_columns.sql delete mode 100644 packages/sync-engine/src/database/migrations/0049_remove_redundant_underscores_from_metadata_tables.sql delete mode 100644 packages/sync-engine/src/database/migrations/0050_rename_id_to_match_stripe_api.sql delete mode 100644 packages/sync-engine/src/database/migrations/0051_remove_webhook_uuid.sql delete mode 100644 packages/sync-engine/src/database/migrations/0052_webhook_url_uniqueness.sql delete mode 100644 packages/sync-engine/src/database/migrations/0053_sync_observability.sql delete mode 100644 packages/sync-engine/src/database/migrations/0054_drop_sync_status.sql delete mode 100644 packages/sync-engine/src/database/migrations/0055_bigint_money_columns.sql delete mode 100644 packages/sync-engine/src/database/migrations/0056_sync_run_closed_at.sql delete mode 100644 packages/sync-engine/src/database/migrations/0057_rename_sync_tables.sql delete mode 100644 packages/sync-engine/src/database/migrations/0058_improve_sync_runs_status.sql delete mode 100644 packages/sync-engine/src/database/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql delete mode 100644 packages/sync-engine/src/database/migrations/0060_sigma_exchange_rates_from_usd.sql delete mode 100644 packages/sync-engine/src/database/migrations/0061_add_page_cursor.sql delete mode 100644 packages/sync-engine/src/database/migrations/0062_sigma_query_runs.sql delete mode 100644 packages/sync-engine/src/database/migrations/0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql delete mode 100644 packages/sync-engine/src/database/migrations/0064_add_created_gte_lte.sql delete mode 100644 packages/sync-engine/src/database/migrations/0065_add_created_lte_to_pk.sql delete mode 100644 packages/sync-engine/src/database/migrations/0066_rate_limits.sql delete mode 100644 packages/sync-engine/src/database/migrations/0067_add_priority_to_sync_obj_runs.sql delete mode 100644 packages/sync-engine/src/database/migrations/0068_sync_obj_progress_view.sql delete mode 100644 packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql diff --git a/packages/sync-engine/src/database/migrate.ts b/packages/sync-engine/src/database/migrate.ts index c31dfd30..04e3b95e 100644 --- a/packages/sync-engine/src/database/migrate.ts +++ b/packages/sync-engine/src/database/migrate.ts @@ -548,6 +548,25 @@ export async function runMigrations(config: MigrationConfig): Promise { const isEmpty = migrationCount.rows[0]?.count === '0' if (isEmpty) { await cleanupSchema(client, syncSchema, config.logger) + } else if (fs.existsSync(migrationsDirectory)) { + const initialFile = fs + .readdirSync(migrationsDirectory) + .filter((f) => f.endsWith('.sql')) + .sort() + .find((f) => parseMigrationId(f) === 0) + if (initialFile) { + const initialSql = fs.readFileSync(path.join(migrationsDirectory, initialFile), 'utf8') + const expectedHash = computeMigrationHash(initialFile, initialSql) + const result = await client.query( + `SELECT hash FROM "${syncSchema}"."_migrations" WHERE id = 0` + ) + if (result.rows.length > 0 && result.rows[0].hash !== expectedHash) { + config.logger?.warn( + 'Initial migration (0) hash changed — resetting schema to reapply from scratch' + ) + await cleanupSchema(client, syncSchema, config.logger) + } + } } } @@ -704,21 +723,32 @@ export async function runMigrationsFromContent( await ensureMigrationsTable(client, syncSchema, tableName) - const appliedMigrations = await getAppliedMigrations(client, syncSchema, tableName) - const appliedIds = new Set(appliedMigrations.map((migration) => migration.id)) + let appliedMigrations = await getAppliedMigrations(client, syncSchema, tableName) const parsedMigrations = parseMigrations(migrations) - for (const applied of appliedMigrations) { - const intended = parsedMigrations.find((migration) => migration.id === applied.id) - if (intended && intended.hash !== applied.hash) { - throw new Error( - `Migration hash mismatch for ${applied.name}: ` + - `expected ${intended.hash}, got ${applied.hash}. ` + - `Migrations cannot be modified after being applied.` - ) + const appliedInitial = appliedMigrations.find((m) => m.id === 0) + const intendedInitial = parsedMigrations.find((m) => m.id === 0) + if (appliedInitial && intendedInitial && appliedInitial.hash !== intendedInitial.hash) { + config.logger?.warn( + 'Initial migration (0) hash changed — resetting schema to reapply from scratch' + ) + await cleanupSchema(client, syncSchema, config.logger) + await ensureMigrationsTable(client, syncSchema, tableName) + appliedMigrations = [] + } else { + for (const applied of appliedMigrations) { + const intended = parsedMigrations.find((migration) => migration.id === applied.id) + if (intended && intended.hash !== applied.hash) { + throw new Error( + `Migration hash mismatch for ${applied.name}: ` + + `expected ${intended.hash}, got ${applied.hash}. ` + + `Migrations cannot be modified after being applied.` + ) + } } } + const appliedIds = new Set(appliedMigrations.map((migration) => migration.id)) const pendingMigrations = parsedMigrations.filter((migration) => !appliedIds.has(migration.id)) if (pendingMigrations.length === 0) { config.logger?.info('No migrations to run') diff --git a/packages/sync-engine/src/database/migrations-embedded.ts b/packages/sync-engine/src/database/migrations-embedded.ts index d14be52c..07cbb476 100644 --- a/packages/sync-engine/src/database/migrations-embedded.ts +++ b/packages/sync-engine/src/database/migrations-embedded.ts @@ -9,282 +9,6 @@ export type EmbeddedMigration = { export const embeddedMigrations: EmbeddedMigration[] = [ { name: '0000_initial_migration.sql', - sql: 'select 1;', - }, - { - name: '0001_products.sql', - sql: 'create table if not exists "stripe"."products" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "description" text,\n "metadata" jsonb,\n "name" text,\n "created" integer,\n "images" jsonb,\n "livemode" boolean,\n "package_dimensions" jsonb,\n "shippable" boolean,\n "statement_descriptor" text,\n "unit_label" text,\n "updated" integer,\n "url" text\n);\n', - }, - { - name: '0002_customers.sql', - sql: 'create table if not exists "stripe"."customers" (\n "id" text primary key,\n "object" text,\n "address" jsonb,\n "description" text,\n "email" text,\n "metadata" jsonb,\n "name" text,\n "phone" text,\n "shipping" jsonb,\n "balance" integer,\n "created" integer,\n "currency" text,\n "default_source" text,\n "delinquent" boolean,\n "discount" jsonb,\n "invoice_prefix" text,\n "invoice_settings" jsonb,\n "livemode" boolean,\n "next_invoice_sequence" integer,\n "preferred_locales" jsonb,\n "tax_exempt" text\n);\n', - }, - { - name: '0003_prices.sql', - sql: 'DO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_type\') THEN\n create type "stripe"."pricing_type" as enum (\'one_time\', \'recurring\');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'pricing_tiers\') THEN\n create type "stripe"."pricing_tiers" as enum (\'graduated\', \'volume\');\n END IF;\n --more types here...\nEND\n$$;\n\n\ncreate table if not exists "stripe"."prices" (\n "id" text primary key,\n "object" text,\n "active" boolean,\n "currency" text,\n "metadata" jsonb,\n "nickname" text,\n "recurring" jsonb,\n "type" stripe.pricing_type,\n "unit_amount" integer,\n "billing_scheme" text,\n "created" integer,\n "livemode" boolean,\n "lookup_key" text,\n "tiers_mode" stripe.pricing_tiers,\n "transform_quantity" jsonb,\n "unit_amount_decimal" text,\n\n "product" text references stripe.products\n);\n\n', - }, - { - name: '0004_subscriptions.sql', - sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'subscription_status\') THEN\n create type "stripe"."subscription_status" as enum (\n \'trialing\',\n \'active\',\n \'canceled\',\n \'incomplete\',\n \'incomplete_expired\',\n \'past_due\',\n \'unpaid\'\n );\n END IF;\nEND\n$$;\n\ncreate table if not exists "stripe"."subscriptions" (\n "id" text primary key,\n "object" text,\n "cancel_at_period_end" boolean,\n "current_period_end" integer,\n "current_period_start" integer,\n "default_payment_method" text,\n "items" jsonb,\n "metadata" jsonb,\n "pending_setup_intent" text,\n "pending_update" jsonb,\n "status" "stripe"."subscription_status", \n "application_fee_percent" double precision,\n "billing_cycle_anchor" integer,\n "billing_thresholds" jsonb,\n "cancel_at" integer,\n "canceled_at" integer,\n "collection_method" text,\n "created" integer,\n "days_until_due" integer,\n "default_source" text,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "ended_at" integer,\n "livemode" boolean,\n "next_pending_invoice_item_invoice" integer,\n "pause_collection" jsonb,\n "pending_invoice_item_interval" jsonb,\n "start_date" integer,\n "transfer_data" jsonb,\n "trial_end" jsonb,\n "trial_start" jsonb,\n\n "schedule" text,\n "customer" text references "stripe"."customers",\n "latest_invoice" text, -- not yet joined\n "plan" text -- not yet joined\n);\n\n', - }, - { - name: '0005_invoices.sql', - sql: '\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = \'invoice_status\') THEN\n create type "stripe"."invoice_status" as enum (\'draft\', \'open\', \'paid\', \'uncollectible\', \'void\');\n END IF;\nEND\n$$;\n\n\ncreate table if not exists "stripe"."invoices" (\n "id" text primary key,\n "object" text,\n "auto_advance" boolean,\n "collection_method" text,\n "currency" text,\n "description" text,\n "hosted_invoice_url" text,\n "lines" jsonb,\n "metadata" jsonb,\n "period_end" integer,\n "period_start" integer,\n "status" "stripe"."invoice_status",\n "total" bigint,\n "account_country" text,\n "account_name" text,\n "account_tax_ids" jsonb,\n "amount_due" bigint,\n "amount_paid" bigint,\n "amount_remaining" bigint,\n "application_fee_amount" bigint,\n "attempt_count" integer,\n "attempted" boolean,\n "billing_reason" text,\n "created" integer,\n "custom_fields" jsonb,\n "customer_address" jsonb,\n "customer_email" text,\n "customer_name" text,\n "customer_phone" text,\n "customer_shipping" jsonb,\n "customer_tax_exempt" text,\n "customer_tax_ids" jsonb,\n "default_tax_rates" jsonb,\n "discount" jsonb,\n "discounts" jsonb,\n "due_date" integer,\n "ending_balance" integer,\n "footer" text,\n "invoice_pdf" text,\n "last_finalization_error" jsonb,\n "livemode" boolean,\n "next_payment_attempt" integer,\n "number" text,\n "paid" boolean,\n "payment_settings" jsonb,\n "post_payment_credit_notes_amount" integer,\n "pre_payment_credit_notes_amount" integer,\n "receipt_number" text,\n "starting_balance" integer,\n "statement_descriptor" text,\n "status_transitions" jsonb,\n "subtotal" integer,\n "tax" integer,\n "total_discount_amounts" jsonb,\n "total_tax_amounts" jsonb,\n "transfer_data" jsonb,\n "webhooks_delivered_at" integer,\n\n "customer" text references "stripe"."customers",\n "subscription" text references "stripe"."subscriptions",\n "payment_intent" text, -- not yet implemented\n "default_payment_method" text, -- not yet implemented\n "default_source" text, -- not yet implemented\n "on_behalf_of" text, -- not yet implemented\n "charge" text -- not yet implemented\n);\n', - }, - { - name: '0006_charges.sql', - sql: '\ncreate table if not exists "stripe".charges (\n id text primary key,\n object text,\n card jsonb,\n paid boolean,\n "order" text,\n amount bigint,\n review text,\n source jsonb,\n status text,\n created integer,\n dispute text,\n invoice text,\n outcome jsonb,\n refunds jsonb,\n updated integer,\n captured boolean,\n currency text,\n customer text,\n livemode boolean,\n metadata jsonb,\n refunded boolean,\n shipping jsonb,\n application text,\n description text,\n destination text,\n failure_code text,\n on_behalf_of text,\n fraud_details jsonb,\n receipt_email text,\n payment_intent text,\n receipt_number text,\n transfer_group text,\n amount_refunded bigint,\n application_fee text,\n failure_message text,\n source_transfer text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n payment_method_details jsonb\n);\n', - }, - { - name: '0007_coupons.sql', - sql: 'create table if not exists "stripe".coupons (\n id text primary key,\n object text,\n name text,\n valid boolean,\n created integer,\n updated integer,\n currency text,\n duration text,\n livemode boolean,\n metadata jsonb,\n redeem_by integer,\n amount_off bigint,\n percent_off double precision,\n times_redeemed bigint,\n max_redemptions bigint,\n duration_in_months bigint,\n percent_off_precise double precision\n);\n', - }, - { - name: '0008_disputes.sql', - sql: 'create table if not exists "stripe".disputes (\n id text primary key,\n object text,\n amount bigint,\n charge text,\n reason text,\n status text,\n created integer,\n updated integer,\n currency text,\n evidence jsonb,\n livemode boolean,\n metadata jsonb,\n evidence_details jsonb,\n balance_transactions jsonb,\n is_charge_refundable boolean\n);\n', - }, - { - name: '0009_events.sql', - sql: 'create table if not exists "stripe".events (\n id text primary key,\n object text,\n data jsonb,\n type text,\n created integer,\n request text,\n updated integer,\n livemode boolean,\n api_version text,\n pending_webhooks bigint\n);\n', - }, - { - name: '0010_payouts.sql', - sql: 'create table if not exists "stripe".payouts (\n id text primary key,\n object text,\n date text,\n type text,\n amount bigint,\n method text,\n status text,\n created integer,\n updated integer,\n currency text,\n livemode boolean,\n metadata jsonb,\n automatic boolean,\n recipient text,\n description text,\n destination text,\n source_type text,\n arrival_date text,\n bank_account jsonb,\n failure_code text,\n transfer_group text,\n amount_reversed bigint,\n failure_message text,\n source_transaction text,\n balance_transaction text,\n statement_descriptor text,\n statement_description text,\n failure_balance_transaction text\n);\n', - }, - { - name: '0011_plans.sql', - sql: 'create table if not exists "stripe"."plans" (\n id text primary key,\n object text,\n name text,\n tiers jsonb,\n active boolean,\n amount bigint,\n created integer,\n product text,\n updated integer,\n currency text,\n "interval" text,\n livemode boolean,\n metadata jsonb,\n nickname text,\n tiers_mode text,\n usage_type text,\n billing_scheme text,\n interval_count bigint,\n aggregate_usage text,\n transform_usage text,\n trial_period_days bigint,\n statement_descriptor text,\n statement_description text\n);\n', - }, - { - name: '0012_add_updated_at.sql', - sql: "create or replace function set_updated_at() returns trigger\n language plpgsql\nas\n$$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\nalter table stripe.subscriptions\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.subscriptions\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.products\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.products\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.customers\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.customers\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.prices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.prices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.invoices\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.invoices\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.charges\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.charges\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.coupons\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.coupons\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.disputes\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.disputes\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.events\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.events\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.payouts\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.payouts\n for each row\n execute procedure set_updated_at();\n\nalter table stripe.plans\n add updated_at timestamptz default timezone('utc'::text, now()) not null;\n\ncreate trigger handle_updated_at\n before update\n on stripe.plans\n for each row\n execute procedure set_updated_at();\n", - }, - { - name: '0013_add_subscription_items.sql', - sql: 'create table if not exists "stripe"."subscription_items" (\n "id" text primary key,\n "object" text,\n "billing_thresholds" jsonb,\n "created" integer,\n "deleted" boolean,\n "metadata" jsonb,\n "quantity" integer,\n "price" text references "stripe"."prices",\n "subscription" text references "stripe"."subscriptions",\n "tax_rates" jsonb\n);', - }, - { - name: '0014_migrate_subscription_items.sql', - sql: 'WITH subscriptions AS (\n select jsonb_array_elements(items->\'data\') as obj from "stripe"."subscriptions"\n)\ninsert into "stripe"."subscription_items"\nselect obj->>\'id\' as "id",\n obj->>\'object\' as "object", \n obj->\'billing_thresholds\' as "billing_thresholds", \n (obj->>\'created\')::INTEGER as "created", \n (obj->>\'deleted\')::BOOLEAN as "deleted", \n obj->\'metadata\' as "metadata", \n (obj->>\'quantity\')::INTEGER as "quantity", \n (obj->\'price\'->>\'id\')::TEXT as "price", \n obj->>\'subscription\' as "subscription", \n obj->\'tax_rates\' as "tax_rates"\nfrom subscriptions\non conflict ("id") \ndo update set "id" = excluded."id",\n "object" = excluded."object",\n "billing_thresholds" = excluded."billing_thresholds",\n "created" = excluded."created",\n "deleted" = excluded."deleted",\n "metadata" = excluded."metadata",\n "quantity" = excluded."quantity",\n "price" = excluded."price",\n "subscription" = excluded."subscription",\n "tax_rates" = excluded."tax_rates"', - }, - { - name: '0015_add_customer_deleted.sql', - sql: 'alter table stripe.customers\n add deleted boolean default false not null;', - }, - { - name: '0016_add_invoice_indexes.sql', - sql: 'CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);', - }, - { - name: '0017_drop_charges_unavailable_columns.sql', - sql: '-- drop columns that are duplicated / not available anymore\n-- card is not available on webhook v.2020-03-02. We can get the detail from payment_method_details\n-- statement_description is not available on webhook v.2020-03-02\nalter table "stripe"."charges"\n drop column if exists "card",\n drop column if exists "statement_description";', - }, - { - name: '0018_setup_intents.sql', - sql: 'create table if not exists "stripe"."setup_intents" (\n id text primary key,\n object text,\n created integer,\n customer text,\n description text,\n payment_method text,\n status text,\n usage text,\n cancellation_reason text,\n latest_attempt text,\n mandate text,\n single_use_mandate text,\n on_behalf_of text\n);\n\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);', - }, - { - name: '0019_payment_methods.sql', - sql: 'create table if not exists "stripe"."payment_methods" (\n id text primary key,\n object text,\n created integer,\n customer text,\n type text,\n billing_details jsonb,\n metadata jsonb,\n card jsonb\n);\n\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);', - }, - { - name: '0020_disputes_payment_intent_created_idx.sql', - sql: 'ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS payment_intent TEXT;\n\nCREATE INDEX IF NOT EXISTS stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);', - }, - { - name: '0021_payment_intent.sql', - sql: 'create table if not exists "stripe"."payment_intents" (\n id text primary key,\n object text,\n amount integer,\n amount_capturable integer,\n amount_details jsonb,\n amount_received integer,\n application text,\n application_fee_amount integer,\n automatic_payment_methods text,\n canceled_at integer,\n cancellation_reason text,\n capture_method text,\n client_secret text,\n confirmation_method text,\n created integer,\n currency text,\n customer text,\n description text,\n invoice text,\n last_payment_error text,\n livemode boolean,\n metadata jsonb,\n next_action text,\n on_behalf_of text,\n payment_method text,\n payment_method_options jsonb,\n payment_method_types jsonb,\n processing text,\n receipt_email text,\n review text,\n setup_future_usage text,\n shipping jsonb,\n statement_descriptor text,\n statement_descriptor_suffix text,\n status text,\n transfer_data jsonb,\n transfer_group text\n);\n\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);', - }, - { - name: '0022_adjust_plans.sql', - sql: 'ALTER TABLE if exists "stripe"."plans" DROP COLUMN name;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN updated;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN tiers;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_descriptor;\nALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_description;', - }, - { - name: '0023_invoice_deleted.sql', - sql: 'ALTER TYPE "stripe"."invoice_status" ADD VALUE \'deleted\';', - }, - { - name: '0024_subscription_schedules.sql', - sql: "do $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'subscription_schedule_status') THEN\n create type \"stripe\".\"subscription_schedule_status\" as enum ('not_started', 'active', 'completed', 'released', 'canceled');\n END IF;\nEND\n$$;\n\ncreate table if not exists\n \"stripe\".\"subscription_schedules\" (\n id text primary key,\n object text,\n application text,\n canceled_at integer,\n completed_at integer,\n created integer not null,\n current_phase jsonb,\n customer text not null,\n default_settings jsonb,\n end_behavior text,\n livemode boolean not null,\n metadata jsonb not null,\n phases jsonb not null,\n released_at integer,\n released_subscription text,\n status stripe.subscription_schedule_status not null,\n subscription text,\n test_clock text\n );", - }, - { - name: '0025_tax_ids.sql', - sql: 'create table if not exists\n "stripe"."tax_ids" (\n "id" text primary key,\n "object" text,\n "country" text,\n "customer" text,\n "type" text,\n "value" text,\n "created" integer not null,\n "livemode" boolean,\n "owner" jsonb\n );\n\ncreate index stripe_tax_ids_customer_idx on "stripe"."tax_ids" using btree (customer);', - }, - { - name: '0026_credit_notes.sql', - sql: 'create table if not exists\n "stripe"."credit_notes" (\n "id" text primary key,\n object text,\n amount integer,\n amount_shipping integer,\n created integer,\n currency text,\n customer text,\n customer_balance_transaction text,\n discount_amount integer,\n discount_amounts jsonb,\n invoice text,\n lines jsonb,\n livemode boolean,\n memo text,\n metadata jsonb,\n number text,\n out_of_band_amount integer,\n pdf text,\n reason text,\n refund text,\n shipping_cost jsonb,\n status text,\n subtotal integer,\n subtotal_excluding_tax integer,\n tax_amounts jsonb,\n total integer,\n total_excluding_tax integer,\n type text,\n voided_at text\n );\n\ncreate index stripe_credit_notes_customer_idx on "stripe"."credit_notes" using btree (customer);\n\ncreate index stripe_credit_notes_invoice_idx on "stripe"."credit_notes" using btree (invoice);', - }, - { - name: '0027_add_marketing_features_to_products.sql', - sql: 'ALTER TABLE IF EXISTS stripe.products ADD COLUMN IF NOT EXISTS marketing_features JSONB;\n\n', - }, - { - name: '0028_early_fraud_warning.sql', - sql: 'create table\n if not exists "stripe"."early_fraud_warnings" (\n "id" text primary key,\n object text,\n actionable boolean,\n charge text,\n created integer,\n fraud_type text,\n livemode boolean,\n payment_intent text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_early_fraud_warnings_charge_idx on "stripe"."early_fraud_warnings" using btree (charge);\n\ncreate index stripe_early_fraud_warnings_payment_intent_idx on "stripe"."early_fraud_warnings" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.early_fraud_warnings\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0029_reviews.sql', - sql: 'create table\n if not exists "stripe"."reviews" (\n "id" text primary key,\n object text,\n billing_zip text,\n charge text,\n created integer,\n closed_reason text,\n livemode boolean,\n ip_address text,\n ip_address_location jsonb,\n open boolean,\n opened_reason text,\n payment_intent text,\n reason text,\n session text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_reviews_charge_idx on "stripe"."reviews" using btree (charge);\n\ncreate index stripe_reviews_payment_intent_idx on "stripe"."reviews" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.reviews\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0030_refunds.sql', - sql: 'create table\n if not exists "stripe"."refunds" (\n "id" text primary key,\n object text,\n amount integer,\n balance_transaction text,\n charge text,\n created integer,\n currency text,\n destination_details jsonb,\n metadata jsonb,\n payment_intent text,\n reason text,\n receipt_number text,\n source_transfer_reversal text,\n status text,\n transfer_reversal text,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null\n );\n\ncreate index stripe_refunds_charge_idx on "stripe"."refunds" using btree (charge);\n\ncreate index stripe_refunds_payment_intent_idx on "stripe"."refunds" using btree (payment_intent);\n\ncreate trigger handle_updated_at\n before update\n on stripe.refunds\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0031_add_default_price.sql', - sql: 'alter table "stripe"."products"\nadd column IF NOT EXISTS "default_price" text;\n', - }, - { - name: '0032_update_subscription_items.sql', - sql: 'ALTER TABLE "stripe"."subscription_items"\nADD COLUMN IF NOT EXISTS "current_period_end" integer,\nADD COLUMN IF NOT EXISTS "current_period_start" integer;\n', - }, - { - name: '0033_add_last_synced_at.sql', - sql: '-- Add last_synced_at column to all Stripe tables for tracking sync status\n\n-- Charges\nalter table "stripe"."charges"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Coupons\nalter table "stripe"."coupons"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Credit Notes\nalter table "stripe"."credit_notes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Customers\nalter table "stripe"."customers"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Disputes\nalter table "stripe"."disputes"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Early Fraud Warnings\nalter table "stripe"."early_fraud_warnings"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Events\nalter table "stripe"."events"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Invoices\nalter table "stripe"."invoices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Intents\nalter table "stripe"."payment_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payment Methods\nalter table "stripe"."payment_methods"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Payouts\nalter table "stripe"."payouts"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Plans\nalter table "stripe"."plans"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Prices\nalter table "stripe"."prices"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Products\nalter table "stripe"."products"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Refunds\nalter table "stripe"."refunds"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Reviews\nalter table "stripe"."reviews"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Setup Intents\nalter table "stripe"."setup_intents"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Items\nalter table "stripe"."subscription_items"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscription Schedules\nalter table "stripe"."subscription_schedules"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Subscriptions\nalter table "stripe"."subscriptions"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n\n-- Tax IDs\nalter table "stripe"."tax_ids"\nadd column IF NOT EXISTS "last_synced_at" timestamptz;\n', - }, - { - name: '0034_remove_foreign_keys.sql', - sql: '-- Remove all foreign key constraints\n\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS "subscriptions_customer_fkey";\n\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS "prices_product_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_customer_fkey";\n\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_subscription_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_price_fkey";\n\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_subscription_fkey";\n', - }, - { - name: '0035_checkout_sessions.sql', - sql: 'create table\n if not exists "stripe"."checkout_sessions" (\n "id" text primary key,\n "object" text,\n "adaptive_pricing" jsonb,\n "after_expiration" jsonb,\n "allow_promotion_codes" boolean,\n "amount_subtotal" integer,\n "amount_total" integer,\n "automatic_tax" jsonb,\n "billing_address_collection" text,\n "cancel_url" text,\n "client_reference_id" text,\n "client_secret" text,\n "collected_information" jsonb,\n "consent" jsonb,\n "consent_collection" jsonb,\n "created" integer,\n "currency" text,\n "currency_conversion" jsonb,\n "custom_fields" jsonb,\n "custom_text" jsonb,\n "customer" text,\n "customer_creation" text,\n "customer_details" jsonb,\n "customer_email" text,\n "discounts" jsonb,\n "expires_at" integer,\n "invoice" text,\n "invoice_creation" jsonb,\n "livemode" boolean,\n "locale" text,\n "metadata" jsonb,\n "mode" text,\n "optional_items" jsonb,\n "payment_intent" text,\n "payment_link" text,\n "payment_method_collection" text,\n "payment_method_configuration_details" jsonb,\n "payment_method_options" jsonb,\n "payment_method_types" jsonb,\n "payment_status" text,\n "permissions" jsonb,\n "phone_number_collection" jsonb,\n "presentment_details" jsonb,\n "recovered_from" text,\n "redirect_on_completion" text,\n "return_url" text,\n "saved_payment_method_options" jsonb,\n "setup_intent" text,\n "shipping_address_collection" jsonb,\n "shipping_cost" jsonb,\n "shipping_details" jsonb,\n "shipping_options" jsonb,\n "status" text,\n "submit_type" text,\n "subscription" text,\n "success_url" text,\n "tax_id_collection" jsonb,\n "total_details" jsonb,\n "ui_mode" text,\n "url" text,\n "wallet_options" jsonb,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_checkout_sessions_customer_idx on "stripe"."checkout_sessions" using btree (customer);\ncreate index stripe_checkout_sessions_subscription_idx on "stripe"."checkout_sessions" using btree (subscription);\ncreate index stripe_checkout_sessions_payment_intent_idx on "stripe"."checkout_sessions" using btree (payment_intent);\ncreate index stripe_checkout_sessions_invoice_idx on "stripe"."checkout_sessions" using btree (invoice);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_sessions\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0036_checkout_session_line_items.sql', - sql: 'create table if not exists "stripe"."checkout_session_line_items" (\n "id" text primary key,\n "object" text,\n "amount_discount" integer,\n "amount_subtotal" integer,\n "amount_tax" integer,\n "amount_total" integer,\n "currency" text,\n "description" text,\n "price" text references "stripe"."prices" on delete cascade,\n "quantity" integer,\n "checkout_session" text references "stripe"."checkout_sessions" on delete cascade,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n);\n\ncreate index stripe_checkout_session_line_items_session_idx on "stripe"."checkout_session_line_items" using btree (checkout_session);\ncreate index stripe_checkout_session_line_items_price_idx on "stripe"."checkout_session_line_items" using btree (price);\n\ncreate trigger handle_updated_at\n before update\n on stripe.checkout_session_line_items\n for each row\n execute procedure set_updated_at(); ', - }, - { - name: '0037_add_features.sql', - sql: 'create table\n if not exists "stripe"."features" (\n "id" text primary key,\n object text,\n livemode boolean,\n name text,\n lookup_key text unique,\n active boolean,\n metadata jsonb,\n updated_at timestamptz default timezone(\'utc\'::text, now()) not null,\n last_synced_at timestamptz\n );\n\ncreate trigger handle_updated_at\n before update\n on stripe.features\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0038_active_entitlement.sql', - sql: 'create table\n if not exists "stripe"."active_entitlements" (\n "id" text primary key,\n "object" text,\n "livemode" boolean,\n "feature" text,\n "customer" text,\n "lookup_key" text unique,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_active_entitlements_customer_idx on "stripe"."active_entitlements" using btree (customer);\ncreate index stripe_active_entitlements_feature_idx on "stripe"."active_entitlements" using btree (feature);\n\ncreate trigger handle_updated_at\n before update\n on stripe.active_entitlements\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0039_add_paused_to_subscription_status.sql', - sql: 'ALTER TYPE "stripe"."subscription_status" ADD VALUE \'paused\';', - }, - { - name: '0040_managed_webhooks.sql', - sql: 'create table\n if not exists "stripe"."managed_webhooks" (\n "id" text primary key,\n "object" text,\n "uuid" text unique not null,\n "url" text not null,\n "enabled_events" jsonb not null,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text not null,\n "status" text,\n "api_version" text,\n "created" integer,\n "updated_at" timestamptz default timezone(\'utc\'::text, now()) not null,\n "last_synced_at" timestamptz\n );\n\ncreate index stripe_managed_webhooks_uuid_idx on "stripe"."managed_webhooks" using btree (uuid);\ncreate index stripe_managed_webhooks_status_idx on "stripe"."managed_webhooks" using btree (status);\ncreate index stripe_managed_webhooks_enabled_idx on "stripe"."managed_webhooks" using btree (enabled);\n\ncreate trigger handle_updated_at\n before update\n on stripe.managed_webhooks\n for each row\n execute procedure set_updated_at();\n', - }, - { - name: '0041_rename_managed_webhooks.sql', - sql: '-- Rename managed_webhooks table to _managed_webhooks\nalter table if exists "stripe"."managed_webhooks" rename to "_managed_webhooks";\n', - }, - { - name: '0042_convert_to_jsonb_generated_columns.sql', - sql: '-- Convert all tables to use jsonb raw_data as source of truth with generated columns\n-- This migration adds raw_data column and converts all existing columns to generated columns\n\n-- ============================================================================\n-- ACTIVE_ENTITLEMENTS\n-- ============================================================================\n\n-- Add raw_data column\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes (will be recreated on generated columns)\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_feature_idx";\n\n-- Drop unique constraint (will be recreated on generated column)\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS "active_entitlements_lookup_key_key";\n\n-- Drop existing columns and recreate as generated columns\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((raw_data->>\'feature\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- CHARGES\n-- ============================================================================\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((raw_data->>\'order\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (raw_data->\'source\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((raw_data->>\'dispute\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (raw_data->\'outcome\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (raw_data->\'refunds\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((raw_data->>\'captured\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((raw_data->>\'refunded\')::boolean) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (raw_data->\'fraud_details\') STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_refunded\')::bigint) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((raw_data->>\'application_fee\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_details\') STORED;\n\n-- ============================================================================\n-- CHECKOUT_SESSION_LINE_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_session_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_price_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((raw_data->>\'amount_discount\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((raw_data->>\'amount_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((raw_data->>\'checkout_session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\n\n-- ============================================================================\n-- CHECKOUT_SESSIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_subscription_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_payment_intent_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_invoice_idx";\n\n-- Drop and recreate columns as generated (all columns from checkoutSessionSchema)\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (raw_data->\'adaptive_pricing\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (raw_data->\'after_expiration\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((raw_data->>\'allow_promotion_codes\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'amount_subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>\'amount_total\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (raw_data->\'automatic_tax\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((raw_data->>\'billing_address_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((raw_data->>\'cancel_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((raw_data->>\'client_reference_id\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (raw_data->\'collected_information\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (raw_data->\'consent\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (raw_data->\'consent_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (raw_data->\'currency_conversion\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (raw_data->\'custom_text\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((raw_data->>\'customer_creation\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (raw_data->\'customer_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((raw_data->>\'expires_at\')::integer) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_creation\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((raw_data->>\'locale\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((raw_data->>\'mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (raw_data->\'optional_items\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((raw_data->>\'payment_link\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((raw_data->>\'payment_method_collection\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_configuration_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((raw_data->>\'payment_status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (raw_data->\'permissions\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (raw_data->\'phone_number_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (raw_data->\'presentment_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((raw_data->>\'recovered_from\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((raw_data->>\'redirect_on_completion\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((raw_data->>\'return_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'saved_payment_method_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_address_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_options\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((raw_data->>\'submit_type\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((raw_data->>\'success_url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (raw_data->\'tax_id_collection\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (raw_data->\'total_details\') STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((raw_data->>\'ui_mode\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (raw_data->\'wallet_options\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\n\n-- ============================================================================\n-- COUPONS\n-- ============================================================================\n\nALTER TABLE "stripe"."coupons" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((raw_data->>\'valid\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((raw_data->>\'duration\')::text) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((raw_data->>\'redeem_by\')::integer) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_off\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off\')::double precision) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((raw_data->>\'times_redeemed\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((raw_data->>\'max_redemptions\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((raw_data->>\'duration_in_months\')::bigint) STORED;\n\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((raw_data->>\'percent_off_precise\')::double precision) STORED;\n\n-- ============================================================================\n-- CREDIT_NOTES\n-- ============================================================================\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_credit_notes_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((raw_data->>\'amount_shipping\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'customer_balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((raw_data->>\'discount_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'discount_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((raw_data->>\'memo\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((raw_data->>\'out_of_band_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((raw_data->>\'pdf\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((raw_data->>\'refund\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->\'shipping_cost\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'tax_amounts\') STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((raw_data->>\'total\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>\'total_excluding_tax\')::integer) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((raw_data->>\'voided_at\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\n\n-- ============================================================================\n-- CUSTOMERS\n-- ============================================================================\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (raw_data->\'address\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((raw_data->>\'phone\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((raw_data->>\'balance\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((raw_data->>\'delinquent\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((raw_data->>\'invoice_prefix\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (raw_data->\'invoice_settings\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((raw_data->>\'next_invoice_sequence\')::integer) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (raw_data->\'preferred_locales\') STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\n-- ============================================================================\n-- DISPUTES\n-- ============================================================================\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_dispute_created_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (raw_data->\'evidence\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (raw_data->\'evidence_details\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (raw_data->\'balance_transactions\') STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((raw_data->>\'is_charge_refundable\')::boolean) STORED;\n\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\n\n-- ============================================================================\n-- EARLY_FRAUD_WARNINGS\n-- ============================================================================\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((raw_data->>\'actionable\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((raw_data->>\'fraud_type\')::text) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\n\n-- ============================================================================\n-- EVENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."events" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (raw_data->\'data\') STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((raw_data->>\'request\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((raw_data->>\'api_version\')::text) STORED;\n\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((raw_data->>\'pending_webhooks\')::bigint) STORED;\n\n-- ============================================================================\n-- FEATURES\n-- ============================================================================\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop unique constraint\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS "features_lookup_key_key";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate unique constraint\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\n\n-- ============================================================================\n-- INVOICES\n-- ============================================================================\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_invoices_subscription_idx";\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((raw_data->>\'auto_advance\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((raw_data->>\'hosted_invoice_url\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->\'lines\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((raw_data->>\'period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((raw_data->>\'period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((raw_data->>\'total\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((raw_data->>\'account_country\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((raw_data->>\'account_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'account_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_due\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_paid\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_remaining\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((raw_data->>\'attempt_count\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((raw_data->>\'attempted\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((raw_data->>\'billing_reason\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->\'custom_fields\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (raw_data->\'customer_address\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>\'customer_email\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((raw_data->>\'customer_name\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((raw_data->>\'customer_phone\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (raw_data->\'customer_shipping\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((raw_data->>\'customer_tax_exempt\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->\'customer_tax_ids\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->\'discounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((raw_data->>\'due_date\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((raw_data->>\'ending_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((raw_data->>\'footer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((raw_data->>\'invoice_pdf\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (raw_data->\'last_finalization_error\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((raw_data->>\'next_payment_attempt\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>\'number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>\'paid\')::boolean) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (raw_data->\'payment_settings\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((raw_data->>\'starting_balance\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (raw_data->\'status_transitions\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>\'subtotal\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((raw_data->>\'tax\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_discount_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->\'total_tax_amounts\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((raw_data->>\'webhooks_delivered_at\')::integer) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\n\n-- ============================================================================\n-- PAYMENT_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_customer_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_payment_intents_invoice_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((raw_data->>\'amount_capturable\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (raw_data->\'amount_details\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((raw_data->>\'amount_received\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((raw_data->>\'application_fee_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((raw_data->>\'automatic_payment_methods\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((raw_data->>\'capture_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>\'client_secret\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((raw_data->>\'confirmation_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>\'invoice\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((raw_data->>\'last_payment_error\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((raw_data->>\'next_action\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_options\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->\'payment_method_types\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((raw_data->>\'processing\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>\'receipt_email\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>\'review\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((raw_data->>\'setup_future_usage\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->\'shipping\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor_suffix\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\n\n-- ============================================================================\n-- PAYMENT_METHODS\n-- ============================================================================\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_payment_methods_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (raw_data->\'billing_details\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (raw_data->\'card\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\n\n-- ============================================================================\n-- PAYOUTS\n-- ============================================================================\n\nALTER TABLE "stripe"."payouts" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((raw_data->>\'date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((raw_data->>\'method\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((raw_data->>\'automatic\')::boolean) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((raw_data->>\'recipient\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>\'destination\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((raw_data->>\'source_type\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((raw_data->>\'arrival_date\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (raw_data->\'bank_account\') STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>\'failure_code\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>\'transfer_group\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((raw_data->>\'amount_reversed\')::bigint) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>\'failure_message\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((raw_data->>\'source_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'failure_balance_transaction\')::text) STORED;\n\n-- ============================================================================\n-- PLANS\n-- ============================================================================\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (raw_data->\'tiers\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>\'amount\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((raw_data->>\'interval\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((raw_data->>\'usage_type\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((raw_data->>\'interval_count\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((raw_data->>\'aggregate_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((raw_data->>\'transform_usage\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((raw_data->>\'trial_period_days\')::bigint) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>\'statement_description\')::text) STORED;\n\n-- ============================================================================\n-- PRICES\n-- ============================================================================\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum types converted to text)\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>\'nickname\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (raw_data->\'recurring\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((raw_data->>\'unit_amount\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>\'billing_scheme\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>\'lookup_key\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>\'tiers_mode\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (raw_data->\'transform_quantity\') STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((raw_data->>\'unit_amount_decimal\')::text) STORED;\n\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>\'product\')::text) STORED;\n\n-- ============================================================================\n-- PRODUCTS\n-- ============================================================================\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>\'active\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((raw_data->>\'default_price\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>\'name\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (raw_data->\'images\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (raw_data->\'marketing_features\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (raw_data->\'package_dimensions\') STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((raw_data->>\'shippable\')::boolean) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>\'statement_descriptor\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((raw_data->>\'unit_label\')::text) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>\'updated\')::integer) STORED;\n\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>\'url\')::text) STORED;\n\n-- ============================================================================\n-- REFUNDS\n-- ============================================================================\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_refunds_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>\'amount\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>\'balance_transaction\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>\'currency\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (raw_data->\'destination_details\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>\'receipt_number\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'source_transfer_reversal\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>\'transfer_reversal\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\n\n-- ============================================================================\n-- REVIEWS\n-- ============================================================================\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_charge_idx";\nDROP INDEX IF EXISTS "stripe"."stripe_reviews_payment_intent_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((raw_data->>\'billing_zip\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>\'charge\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((raw_data->>\'closed_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((raw_data->>\'ip_address\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (raw_data->\'ip_address_location\') STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((raw_data->>\'open\')::boolean) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((raw_data->>\'opened_reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>\'payment_intent\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>\'reason\')::text) STORED;\n\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((raw_data->>\'session\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\n\n-- ============================================================================\n-- SETUP_INTENTS\n-- ============================================================================\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_setup_intents_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>\'description\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>\'payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((raw_data->>\'usage\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>\'cancellation_reason\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((raw_data->>\'latest_attempt\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((raw_data->>\'mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((raw_data->>\'single_use_mandate\')::text) STORED;\n\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>\'on_behalf_of\')::text) STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\n\n-- ============================================================================\n-- SUBSCRIPTION_ITEMS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>\'deleted\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>\'quantity\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>\'price\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTION_SCHEDULES\n-- ============================================================================\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>\'application\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((raw_data->>\'completed_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (raw_data->\'current_phase\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (raw_data->\'default_settings\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((raw_data->>\'end_behavior\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (raw_data->\'phases\') STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((raw_data->>\'released_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((raw_data->>\'released_subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>\'subscription\')::text) STORED;\n\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((raw_data->>\'test_clock\')::text) STORED;\n\n-- ============================================================================\n-- SUBSCRIPTIONS\n-- ============================================================================\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop and recreate columns as generated (enum status converted to text)\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((raw_data->>\'cancel_at_period_end\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_end\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>\'current_period_start\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>\'default_payment_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (raw_data->\'items\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->\'metadata\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((raw_data->>\'pending_setup_intent\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (raw_data->\'pending_update\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>\'status\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((raw_data->>\'application_fee_percent\')::double precision) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((raw_data->>\'billing_cycle_anchor\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->\'billing_thresholds\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((raw_data->>\'cancel_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>\'canceled_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>\'collection_method\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((raw_data->>\'days_until_due\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>\'default_source\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->\'default_tax_rates\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->\'discount\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((raw_data->>\'ended_at\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (raw_data->\'pause_collection\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (raw_data->\'pending_invoice_item_interval\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((raw_data->>\'start_date\')::integer) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->\'transfer_data\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (raw_data->\'trial_end\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (raw_data->\'trial_start\') STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((raw_data->>\'schedule\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((raw_data->>\'latest_invoice\')::text) STORED;\n\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((raw_data->>\'plan\')::text) STORED;\n\n-- ============================================================================\n-- TAX_IDS\n-- ============================================================================\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "raw_data" jsonb;\n\n-- Drop indexes\nDROP INDEX IF EXISTS "stripe"."stripe_tax_ids_customer_idx";\n\n-- Drop and recreate columns as generated\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>\'object\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>\'customer\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((raw_data->>\'value\')::text) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>\'livemode\')::boolean) STORED;\n\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (raw_data->\'owner\') STORED;\n\n-- Recreate indexes\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n\n', - }, - { - name: '0043_add_account_id.sql', - sql: '-- Add _account_id column to all tables to track which Stripe account each record belongs to\n-- Column is nullable for backward compatibility with existing data\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."_managed_webhooks" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\nALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "_account_id" TEXT;\n\n', - }, - { - name: '0044_make_account_id_required.sql', - sql: '-- Make _account_id required by:\n-- 1. Deleting all rows where _account_id IS NULL\n-- 2. Setting _account_id to NOT NULL\n\n-- Delete rows with null _account_id\nDELETE FROM "stripe"."active_entitlements" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."charges" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_session_line_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."checkout_sessions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."credit_notes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."customers" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."disputes" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."early_fraud_warnings" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."features" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."invoices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."_managed_webhooks" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."payment_methods" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."plans" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."prices" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."products" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."refunds" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."reviews" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."setup_intents" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_items" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscription_schedules" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."subscriptions" WHERE "_account_id" IS NULL;\nDELETE FROM "stripe"."tax_ids" WHERE "_account_id" IS NULL;\n\n-- Make _account_id NOT NULL\nALTER TABLE "stripe"."active_entitlements" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."charges" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_session_line_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."checkout_sessions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."credit_notes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."customers" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."disputes" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."early_fraud_warnings" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."features" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."invoices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."_managed_webhooks" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."payment_methods" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."plans" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."prices" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."products" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."refunds" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."reviews" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."setup_intents" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_items" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscription_schedules" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."subscriptions" ALTER COLUMN "_account_id" SET NOT NULL;\nALTER TABLE "stripe"."tax_ids" ALTER COLUMN "_account_id" SET NOT NULL;\n\n', - }, - { - name: '0045_sync_status.sql', - sql: "-- Create _sync_status metadata table for tracking incremental sync cursors\n-- This table tracks the state and progress of each resource's synchronization\n\nCREATE TABLE IF NOT EXISTS \"stripe\".\"_sync_status\" (\n id serial PRIMARY KEY,\n resource text UNIQUE NOT NULL,\n status text CHECK (status IN ('idle', 'running', 'complete', 'error')) DEFAULT 'idle',\n last_synced_at timestamptz DEFAULT now(),\n last_incremental_cursor timestamptz,\n error_message text,\n updated_at timestamptz DEFAULT now()\n);\n\n-- Use existing set_updated_at() function created in migration 0012\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON \"stripe\".\"_sync_status\"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n", - }, - { - name: '0046_sync_status_per_account.sql', - sql: '-- Add _account_id to _sync_status table to track sync cursors per account\n-- This enables proper cursor isolation when syncing multiple Stripe accounts\n--\n-- Breaking change: All existing cursor data will be deleted (clean slate)\n-- Next sync will perform a full backfill for each account\n\n-- Step 1: Delete all existing cursor data\nDELETE FROM "stripe"."_sync_status";\n\n-- Step 2: Add _account_id column\nALTER TABLE "stripe"."_sync_status" ADD COLUMN "_account_id" TEXT NOT NULL;\n\n-- Step 3: Drop existing unique constraint on resource\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_key;\n\n-- Step 4: Add new composite unique constraint on (resource, _account_id)\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "_account_id");\n\n-- Step 5: Add index for efficient lookups\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "_account_id");\n\n-- Step 6: Create accounts table to track Stripe accounts (JSONB with generated columns)\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n id TEXT PRIMARY KEY,\n raw_data JSONB NOT NULL,\n first_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n -- Generated columns extracted from raw_data\n business_name TEXT GENERATED ALWAYS AS ((raw_data->\'business_profile\'->>\'name\')::text) STORED,\n email TEXT GENERATED ALWAYS AS ((raw_data->>\'email\')::text) STORED,\n type TEXT GENERATED ALWAYS AS ((raw_data->>\'type\')::text) STORED,\n charges_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'charges_enabled\')::boolean) STORED,\n payouts_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'payouts_enabled\')::boolean) STORED,\n details_submitted BOOLEAN GENERATED ALWAYS AS ((raw_data->>\'details_submitted\')::boolean) STORED,\n country TEXT GENERATED ALWAYS AS ((raw_data->>\'country\')::text) STORED,\n default_currency TEXT GENERATED ALWAYS AS ((raw_data->>\'default_currency\')::text) STORED,\n created INTEGER GENERATED ALWAYS AS ((raw_data->>\'created\')::integer) STORED\n);\n\n-- Step 7: Add index for account name lookups\nCREATE INDEX IF NOT EXISTS idx_accounts_business_name\n ON "stripe"."accounts" (business_name);\n\n-- Step 8: Add updated_at trigger for accounts\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."accounts"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at();\n\n-- Step 9: Backfill accounts from existing data tables\nINSERT INTO "stripe"."accounts" (id, raw_data, first_synced_at, last_synced_at)\nSELECT DISTINCT\n "_account_id" as id,\n jsonb_build_object(\'id\', "_account_id", \'type\', \'unknown\') as raw_data,\n now() as first_synced_at,\n now() as last_synced_at\nFROM "stripe"."products"\nWHERE "_account_id" IS NOT NULL\nON CONFLICT (id) DO NOTHING;\n\n-- Step 10: Add foreign key constraints from data tables to accounts\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Step 11: Add foreign key from _sync_status to accounts\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n', - }, - { - name: '0047_api_key_hashes.sql', - sql: '-- Add api_key_hashes array column to accounts table\n-- This stores SHA-256 hashes of Stripe API keys for fast account lookups\n-- Enables lookup of account ID by API key hash without making Stripe API calls\n-- Supports multiple API keys per account (test/live keys, rotated keys, etc.)\n\n-- Step 1: Add api_key_hashes column as TEXT array\nALTER TABLE "stripe"."accounts" ADD COLUMN "api_key_hashes" TEXT[] DEFAULT \'{}\';\n\n-- Step 2: Create GIN index for fast array containment lookups\n-- This enables efficient queries like: WHERE \'hash_value\' = ANY(api_key_hashes)\nCREATE INDEX IF NOT EXISTS idx_accounts_api_key_hashes\n ON "stripe"."accounts" USING GIN (api_key_hashes);\n', - }, - { - name: '0048_rename_reserved_columns.sql', - sql: '-- Rename all reserved column names to use underscore prefix\n-- This clearly distinguishes system-managed columns from user-accessible data\n--\n-- Changes:\n-- - id -> _id (primary key for all tables)\n-- - raw_data -> _raw_data (JSONB source of truth)\n-- - last_synced_at -> _last_synced_at (sync timestamp)\n-- - updated_at -> _updated_at (last update timestamp)\n-- - _account_id remains unchanged (already has underscore prefix)\n\n-- ============================================================================\n-- STEP 1: RENAME BASE COLUMNS FOR ALL TABLES\n-- ============================================================================\n\n-- This renames id, raw_data, last_synced_at, and updated_at for all tables\n-- PostgreSQL automatically updates primary keys, foreign keys, and indexes\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- charges\nALTER TABLE "stripe"."charges" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."charges" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."charges" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."charges" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."credit_notes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- coupons\nALTER TABLE "stripe"."coupons" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."coupons" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- customers\nALTER TABLE "stripe"."customers" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."customers" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."customers" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."customers" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- disputes\nALTER TABLE "stripe"."disputes" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."disputes" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- events\nALTER TABLE "stripe"."events" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."events" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."events" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."events" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- features\nALTER TABLE "stripe"."features" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."features" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."features" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."features" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- invoices\nALTER TABLE "stripe"."invoices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."invoices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- _managed_webhooks\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payment_methods" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- payouts\nALTER TABLE "stripe"."payouts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."payouts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- plans\nALTER TABLE "stripe"."plans" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."plans" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."plans" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."plans" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- prices\nALTER TABLE "stripe"."prices" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."prices" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."prices" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."prices" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- products\nALTER TABLE "stripe"."products" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."products" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."products" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."products" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- refunds\nALTER TABLE "stripe"."refunds" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."refunds" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- reviews\nALTER TABLE "stripe"."reviews" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."reviews" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."setup_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."subscriptions" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."tax_ids" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\n\n-- Metadata Tables\n\n-- _sync_status\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- accounts\nALTER TABLE "stripe"."accounts" RENAME COLUMN "id" TO "_id";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "raw_data" TO "_raw_data";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "last_synced_at" TO "_last_synced_at";\nALTER TABLE "stripe"."accounts" RENAME COLUMN "updated_at" TO "_updated_at";\n\n-- ============================================================================\n-- STEP 1.5: UPDATE TRIGGER FUNCTION TO USE NEW COLUMN NAME\n-- ============================================================================\n\n-- Now that all columns are renamed, update the trigger function to reference _updated_at\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new._updated_at = now();\n return NEW;\nend;\n$$;\n\n-- ============================================================================\n-- STEP 2: RECREATE GENERATED COLUMNS TO REFERENCE _raw_data\n-- ============================================================================\n\n-- All generated columns must be dropped and recreated to reference the new\n-- _raw_data column name instead of raw_data\n\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months";\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt";\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable";\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version";\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal";\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated";\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason";\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate";\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription";\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice";\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode";\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner";\n\n-- Add generated columns back with _raw_data references\n\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((_raw_data->>\'feature\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((_raw_data->>\'order\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (_raw_data->\'source\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((_raw_data->>\'dispute\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (_raw_data->\'outcome\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (_raw_data->\'refunds\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((_raw_data->>\'captured\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((_raw_data->>\'refunded\')::boolean) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (_raw_data->\'fraud_details\') STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_refunded\')::bigint) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((_raw_data->>\'application_fee\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_details\') STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((_raw_data->>\'checkout_session\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (_raw_data->\'adaptive_pricing\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (_raw_data->\'after_expiration\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((_raw_data->>\'allow_promotion_codes\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (_raw_data->\'automatic_tax\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((_raw_data->>\'billing_address_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((_raw_data->>\'cancel_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((_raw_data->>\'client_reference_id\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (_raw_data->\'collected_information\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (_raw_data->\'consent\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'consent_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (_raw_data->\'currency_conversion\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_text\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((_raw_data->>\'customer_creation\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((_raw_data->>\'expires_at\')::integer) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_creation\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((_raw_data->>\'locale\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((_raw_data->>\'mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (_raw_data->\'optional_items\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((_raw_data->>\'payment_link\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method_collection\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_configuration_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((_raw_data->>\'payment_status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (_raw_data->\'permissions\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'phone_number_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (_raw_data->\'presentment_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((_raw_data->>\'recovered_from\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((_raw_data->>\'redirect_on_completion\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((_raw_data->>\'return_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'saved_payment_method_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'setup_intent\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_address_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_options\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((_raw_data->>\'submit_type\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((_raw_data->>\'success_url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_id_collection\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (_raw_data->\'total_details\') STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((_raw_data->>\'ui_mode\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (_raw_data->\'wallet_options\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((_raw_data->>\'valid\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((_raw_data->>\'duration\')::text) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((_raw_data->>\'redeem_by\')::integer) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_off\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off\')::double precision) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((_raw_data->>\'times_redeemed\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((_raw_data->>\'max_redemptions\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((_raw_data->>\'duration_in_months\')::bigint) STORED;\nALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((_raw_data->>\'percent_off_precise\')::double precision) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'customer_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discount_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((_raw_data->>\'memo\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((_raw_data->>\'pdf\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((_raw_data->>\'refund\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping_cost\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_amounts\') STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((_raw_data->>\'total\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::integer) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((_raw_data->>\'voided_at\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (_raw_data->\'address\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((_raw_data->>\'email\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((_raw_data->>\'phone\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((_raw_data->>\'balance\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((_raw_data->>\'delinquent\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_prefix\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'invoice_settings\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((_raw_data->>\'next_invoice_sequence\')::integer) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (_raw_data->\'preferred_locales\') STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (_raw_data->\'evidence_details\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (_raw_data->\'balance_transactions\') STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((_raw_data->>\'is_charge_refundable\')::boolean) STORED;\nALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((_raw_data->>\'actionable\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((_raw_data->>\'fraud_type\')::text) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (_raw_data->\'data\') STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((_raw_data->>\'request\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((_raw_data->>\'api_version\')::text) STORED;\nALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((_raw_data->>\'pending_webhooks\')::bigint) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((_raw_data->>\'auto_advance\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((_raw_data->>\'hosted_invoice_url\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->\'lines\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'period_end\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'period_start\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((_raw_data->>\'account_country\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((_raw_data->>\'account_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'account_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_due\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_paid\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_remaining\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((_raw_data->>\'attempt_count\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((_raw_data->>\'attempted\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((_raw_data->>\'billing_reason\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->\'custom_fields\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_address\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>\'customer_email\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((_raw_data->>\'customer_name\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((_raw_data->>\'customer_phone\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_shipping\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>\'customer_tax_exempt\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->\'customer_tax_ids\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->\'discounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((_raw_data->>\'due_date\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((_raw_data->>\'footer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((_raw_data->>\'invoice_pdf\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (_raw_data->\'last_finalization_error\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((_raw_data->>\'next_payment_attempt\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>\'number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>\'paid\')::boolean) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_settings\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (_raw_data->\'status_transitions\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((_raw_data->>\'tax\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_discount_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->\'total_tax_amounts\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((_raw_data->>\'webhooks_delivered_at\')::integer) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (_raw_data->\'amount_details\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((_raw_data->>\'automatic_payment_methods\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((_raw_data->>\'capture_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>\'client_secret\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((_raw_data->>\'confirmation_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>\'invoice\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((_raw_data->>\'last_payment_error\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((_raw_data->>\'next_action\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_options\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->\'payment_method_types\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((_raw_data->>\'processing\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_email\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>\'review\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((_raw_data->>\'setup_future_usage\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->\'shipping\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor_suffix\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_details\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (_raw_data->\'card\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((_raw_data->>\'date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((_raw_data->>\'method\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((_raw_data->>\'automatic\')::boolean) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((_raw_data->>\'recipient\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>\'destination\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((_raw_data->>\'source_type\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((_raw_data->>\'arrival_date\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (_raw_data->\'bank_account\') STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>\'failure_code\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_group\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_reversed\')::bigint) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>\'failure_message\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'source_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'failure_balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (_raw_data->\'tiers\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((_raw_data->>\'interval\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((_raw_data->>\'usage_type\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((_raw_data->>\'interval_count\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((_raw_data->>\'aggregate_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((_raw_data->>\'transform_usage\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((_raw_data->>\'trial_period_days\')::bigint) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>\'statement_description\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>\'nickname\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (_raw_data->\'recurring\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>\'billing_scheme\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>\'lookup_key\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>\'tiers_mode\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (_raw_data->\'transform_quantity\') STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((_raw_data->>\'unit_amount_decimal\')::text) STORED;\nALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>\'product\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>\'active\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((_raw_data->>\'default_price\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>\'name\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (_raw_data->\'images\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (_raw_data->\'marketing_features\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (_raw_data->\'package_dimensions\') STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((_raw_data->>\'shippable\')::boolean) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>\'statement_descriptor\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((_raw_data->>\'unit_label\')::text) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>\'updated\')::integer) STORED;\nALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>\'url\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>\'amount\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>\'balance_transaction\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>\'currency\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (_raw_data->\'destination_details\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>\'receipt_number\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'source_transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>\'transfer_reversal\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((_raw_data->>\'billing_zip\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>\'charge\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((_raw_data->>\'closed_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((_raw_data->>\'ip_address\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (_raw_data->\'ip_address_location\') STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((_raw_data->>\'open\')::boolean) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((_raw_data->>\'opened_reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>\'payment_intent\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>\'reason\')::text) STORED;\nALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((_raw_data->>\'session\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>\'description\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'payment_method\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((_raw_data->>\'usage\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>\'cancellation_reason\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((_raw_data->>\'latest_attempt\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((_raw_data->>\'mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((_raw_data->>\'single_use_mandate\')::text) STORED;\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>\'on_behalf_of\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>\'deleted\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>\'quantity\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>\'price\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'tax_rates\') STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>\'application\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((_raw_data->>\'completed_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (_raw_data->\'current_phase\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (_raw_data->\'default_settings\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((_raw_data->>\'end_behavior\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (_raw_data->\'phases\') STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((_raw_data->>\'released_at\')::integer) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((_raw_data->>\'released_subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>\'subscription\')::text) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((_raw_data->>\'test_clock\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((_raw_data->>\'cancel_at_period_end\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_end\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>\'current_period_start\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>\'default_payment_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (_raw_data->\'items\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->\'metadata\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((_raw_data->>\'pending_setup_intent\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_update\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>\'status\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((_raw_data->>\'application_fee_percent\')::double precision) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((_raw_data->>\'billing_cycle_anchor\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->\'billing_thresholds\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((_raw_data->>\'cancel_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>\'canceled_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>\'collection_method\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((_raw_data->>\'days_until_due\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>\'default_source\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->\'default_tax_rates\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->\'discount\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((_raw_data->>\'ended_at\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((_raw_data->>\'next_pending_invoice_item_invoice\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (_raw_data->\'pause_collection\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (_raw_data->\'pending_invoice_item_interval\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((_raw_data->>\'start_date\')::integer) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->\'transfer_data\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_end\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (_raw_data->\'trial_start\') STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((_raw_data->>\'schedule\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((_raw_data->>\'latest_invoice\')::text) STORED;\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((_raw_data->>\'plan\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>\'object\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((_raw_data->>\'country\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>\'customer\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>\'type\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((_raw_data->>\'value\')::text) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>\'created\')::integer) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>\'livemode\')::boolean) STORED;\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (_raw_data->\'owner\') STORED;\n\n-- ============================================================================\n-- STEP 3: RECREATE INDEXES\n-- ============================================================================\n\nCREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer);\nCREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature);\nCREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session);\nCREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price);\nCREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer);\nCREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription);\nCREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent);\nCREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice);\nCREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer);\nCREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice);\nCREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created);\nCREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge);\nCREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent);\nCREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL;\nCREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer);\nCREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription);\nCREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);\nCREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);\nCREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer);\nCREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge);\nCREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent);\nCREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge);\nCREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent);\nCREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer);\nCREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer);\n', - }, - { - name: '0049_remove_redundant_underscores_from_metadata_tables.sql', - sql: '-- Remove redundant underscore prefixes from columns in metadata tables\n--\n-- For tables that are already prefixed with underscore (indicating they are\n-- metadata/system tables), the underscore prefix on columns is redundant.\n-- This migration removes those redundant prefixes to keep naming cleaner.\n--\n-- Affected tables: _sync_status, _managed_webhooks\n\n-- Create a new trigger function for metadata tables that references updated_at without underscore\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nbegin\n new.updated_at = now();\n return NEW;\nend;\n$$;\n\n-- Update _sync_status table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_status";\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_account_key;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\nDROP INDEX IF EXISTS "stripe"."idx_sync_status_resource_account";\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate constraints and trigger with new column names\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT _sync_status_resource_account_key\n UNIQUE (resource, "account_id");\n\nCREATE INDEX IF NOT EXISTS idx_sync_status_resource_account\n ON "stripe"."_sync_status" (resource, "account_id");\n\nALTER TABLE "stripe"."_sync_status"\n ADD CONSTRAINT fk_sync_status_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_status"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Update _managed_webhooks table\n-- Step 1: Drop constraints and triggers that reference the old column names\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\n\n-- Step 2: Rename columns\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_id" TO "id";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_last_synced_at" TO "last_synced_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_updated_at" TO "updated_at";\nALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_account_id" TO "account_id";\n\n-- Step 3: Recreate foreign key constraint and trigger with new column names\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT fk_managed_webhooks_account\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");\n\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_managed_webhooks"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n', - }, - { - name: '0050_rename_id_to_match_stripe_api.sql', - sql: '-- Rename _id back to id to match Stripe API field names\n--\n-- Migration 0048 added underscore prefixes to all "reserved" columns including id.\n-- However, id is actually a field that comes directly from the Stripe API and should\n-- match the API naming for agent/user comprehension.\n--\n-- Additionally, this migration converts id from a regular column to a GENERATED column\n-- derived from _raw_data->>\'id\', ensuring the raw_data is the single source of truth.\n\n-- ============================================================================\n-- Step 1: Drop all foreign key constraints referencing accounts._id\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS fk_active_entitlements_account;\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS fk_charges_account;\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS fk_checkout_session_line_items_account;\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS fk_checkout_sessions_account;\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS fk_credit_notes_account;\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS fk_customers_account;\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS fk_disputes_account;\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS fk_early_fraud_warnings_account;\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS fk_features_account;\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS fk_invoices_account;\nALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS fk_payment_intents_account;\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS fk_payment_methods_account;\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS fk_plans_account;\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS fk_prices_account;\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS fk_products_account;\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS fk_refunds_account;\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS fk_reviews_account;\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS fk_setup_intents_account;\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS fk_subscription_items_account;\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS fk_subscription_schedules_account;\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS fk_subscriptions_account;\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS fk_tax_ids_account;\nALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;\n\n-- ============================================================================\n-- Step 2: Convert accounts._id to generated column accounts.id\n-- ============================================================================\n\nALTER TABLE "stripe"."accounts" DROP CONSTRAINT IF EXISTS accounts_pkey;\nALTER TABLE "stripe"."accounts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."accounts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."accounts" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 3: Convert _id to generated column id for all Stripe entity tables\n-- ============================================================================\n\n-- active_entitlements\nALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS active_entitlements_pkey;\nALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."active_entitlements" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."active_entitlements" ADD PRIMARY KEY (id);\n\n-- charges\nALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS charges_pkey;\nALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."charges" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."charges" ADD PRIMARY KEY (id);\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS checkout_session_line_items_pkey;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" ADD PRIMARY KEY (id);\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS checkout_sessions_pkey;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."checkout_sessions" ADD PRIMARY KEY (id);\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS credit_notes_pkey;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."credit_notes" ADD PRIMARY KEY (id);\n\n-- coupons\nALTER TABLE "stripe"."coupons" DROP CONSTRAINT IF EXISTS coupons_pkey;\nALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."coupons" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."coupons" ADD PRIMARY KEY (id);\n\n-- customers\nALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS customers_pkey;\nALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."customers" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."customers" ADD PRIMARY KEY (id);\n\n-- disputes\nALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS disputes_pkey;\nALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."disputes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."disputes" ADD PRIMARY KEY (id);\n\n-- early_fraud_warnings\nALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS early_fraud_warnings_pkey;\nALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."early_fraud_warnings" ADD PRIMARY KEY (id);\n\n-- events\nALTER TABLE "stripe"."events" DROP CONSTRAINT IF EXISTS events_pkey;\nALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."events" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."events" ADD PRIMARY KEY (id);\n\n-- features\nALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS features_pkey;\nALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."features" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."features" ADD PRIMARY KEY (id);\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS invoices_pkey;\nALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."invoices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."invoices" ADD PRIMARY KEY (id);\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS payment_intents_pkey;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_intents" ADD PRIMARY KEY (id);\n\n-- payment_methods\nALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS payment_methods_pkey;\nALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payment_methods" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payment_methods" ADD PRIMARY KEY (id);\n\n-- payouts\nALTER TABLE "stripe"."payouts" DROP CONSTRAINT IF EXISTS payouts_pkey;\nALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."payouts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."payouts" ADD PRIMARY KEY (id);\n\n-- plans\nALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS plans_pkey;\nALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."plans" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."plans" ADD PRIMARY KEY (id);\n\n-- prices\nALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS prices_pkey;\nALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."prices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."prices" ADD PRIMARY KEY (id);\n\n-- products\nALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS products_pkey;\nALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."products" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."products" ADD PRIMARY KEY (id);\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS refunds_pkey;\nALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."refunds" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."refunds" ADD PRIMARY KEY (id);\n\n-- reviews\nALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS reviews_pkey;\nALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."reviews" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."reviews" ADD PRIMARY KEY (id);\n\n-- setup_intents\nALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS setup_intents_pkey;\nALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."setup_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."setup_intents" ADD PRIMARY KEY (id);\n\n-- subscription_items\nALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS subscription_items_pkey;\nALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_items" ADD PRIMARY KEY (id);\n\n-- subscription_schedules\nALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS subscription_schedules_pkey;\nALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscription_schedules" ADD PRIMARY KEY (id);\n\n-- subscriptions\nALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS subscriptions_pkey;\nALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."subscriptions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."subscriptions" ADD PRIMARY KEY (id);\n\n-- tax_ids\nALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS tax_ids_pkey;\nALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "_id";\nALTER TABLE "stripe"."tax_ids" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>\'id\')::TEXT) STORED;\nALTER TABLE "stripe"."tax_ids" ADD PRIMARY KEY (id);\n\n-- ============================================================================\n-- Step 4: Handle metadata tables\n-- ============================================================================\n\n-- _managed_webhooks (internal metadata table, doesn\'t use _raw_data pattern)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- _sync_status (internal table, uses auto-incrementing id not from Stripe)\n-- Already uses "id" without underscore (migration 0049), no changes needed\n\n-- ============================================================================\n-- Step 5: Recreate all foreign key constraints\n-- ============================================================================\n\nALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\n', - }, - { - name: '0051_remove_webhook_uuid.sql', - sql: '-- Remove UUID from managed webhooks\n-- UUID-based routing is no longer used; webhooks are identified by exact URL match\n-- Legacy webhooks with UUID in URL will be automatically deleted and recreated\n\ndrop index if exists "stripe"."stripe_managed_webhooks_uuid_idx";\n\nalter table "stripe"."_managed_webhooks" drop column if exists "uuid";\n', - }, - { - name: '0052_webhook_url_uniqueness.sql', - sql: '-- Add unique constraint on URL per account to prevent duplicate webhooks at database level\n-- This prevents race conditions where multiple instances try to create webhooks for the same URL\n-- Since UUIDs have been removed from URLs, we can enforce strict uniqueness on the URL column per account\n-- Note: Different accounts can have webhooks with the same URL\n\nalter table "stripe"."_managed_webhooks"\n add constraint managed_webhooks_url_account_unique unique ("url", "account_id");\n', - }, - { - name: '0053_sync_observability.sql', - sql: '-- Observable Sync System: Track sync runs and individual object syncs\n-- Enables observability for long-running syncs (days, not minutes)\n--\n-- Two-level hierarchy:\n-- _sync_run: Parent sync operation (one active per account)\n-- _sync_obj_run: Individual object syncs within a run\n--\n-- Features:\n-- - Only one active run per account (EXCLUDE constraint)\n-- - Configurable object concurrency (max_concurrent)\n-- - Stale detection (is_stale in dashboard view)\n-- - Progress tracking per object\n\n-- Step 1: Create _sync_run table (parent sync operation)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_run" (\n "_account_id" TEXT NOT NULL,\n started_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n status TEXT NOT NULL DEFAULT \'running\' CHECK (status IN (\'running\', \'complete\', \'error\')),\n max_concurrent INTEGER NOT NULL DEFAULT 3,\n completed_at TIMESTAMPTZ,\n error_message TEXT,\n triggered_by TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", started_at),\n\n -- Only one active run per account\n CONSTRAINT one_active_run_per_account\n EXCLUDE ("_account_id" WITH =) WHERE (status = \'running\'),\n\n -- Foreign key to accounts table\n CONSTRAINT fk_sync_run_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)\n);\n\n-- Step 2: Add updated_at trigger for _sync_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 3: Create _sync_obj_run table (individual object syncs)\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_run" (\n "_account_id" TEXT NOT NULL,\n run_started_at TIMESTAMPTZ NOT NULL,\n object TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT \'pending\' CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n processed_count INTEGER DEFAULT 0,\n cursor TEXT,\n error_message TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n PRIMARY KEY ("_account_id", run_started_at, object),\n\n -- Foreign key to parent sync run\n CONSTRAINT fk_sync_obj_run_parent\n FOREIGN KEY ("_account_id", run_started_at) REFERENCES "stripe"."_sync_run" ("_account_id", started_at)\n);\n\n-- Step 4: Add updated_at trigger for _sync_obj_run\n-- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."_sync_obj_run"\n FOR EACH ROW\n EXECUTE PROCEDURE set_updated_at_metadata();\n\n-- Step 5: Create indexes for efficient queries\nCREATE INDEX IF NOT EXISTS idx_sync_run_account_status\n ON "stripe"."_sync_run" ("_account_id", status);\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_run_status\n ON "stripe"."_sync_obj_run" ("_account_id", run_started_at, status);\n\n-- Step 6: Create sync_dashboard view for observability\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n r."_account_id" as account_id,\n r.started_at as run_started_at,\n r.status as run_status,\n r.completed_at as run_completed_at,\n r.max_concurrent,\n r.triggered_by,\n o.object,\n o.status as object_status,\n o.started_at as object_started_at,\n o.completed_at as object_completed_at,\n o.processed_count,\n o.error_message,\n o.updated_at,\n -- Duration in seconds\n EXTRACT(EPOCH FROM (COALESCE(o.completed_at, now()) - o.started_at))::integer as duration_seconds,\n -- Stale detection: running but no update in 5 min\n CASE\n WHEN o.status = \'running\' AND o.updated_at < now() - interval \'5 minutes\'\n THEN true\n ELSE false\n END as is_stale\nFROM "stripe"."_sync_run" r\nLEFT JOIN "stripe"."_sync_obj_run" o\n ON o."_account_id" = r."_account_id"\n AND o.run_started_at = r.started_at;\n', - }, - { - name: '0054_drop_sync_status.sql', - sql: '-- Drop the old _sync_status table\n-- This table has been replaced by _sync_run and _sync_obj_run for better observability\n-- See migration 0053_sync_observability.sql\n\nDROP TABLE IF EXISTS "stripe"."_sync_status";\n', - }, - { - name: '0055_bigint_money_columns.sql', - sql: '-- Fix generated columns: must drop and recreate with ::bigint cast\n-- Money columns that can overflow PostgreSQL integer max (~2.1 billion)\n\n-- checkout_session_line_items\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_discount";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_discount\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_tax";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_tax\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- checkout_sessions\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_subtotal";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_total";\nALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_total\')::bigint) STORED;\n\n-- credit_notes\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount_shipping";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_shipping\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "discount_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'discount_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "out_of_band_amount";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'out_of_band_amount\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal_excluding_tax\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>\'total\')::bigint) STORED;\nALTER TABLE "stripe"."credit_notes" DROP COLUMN "total_excluding_tax";\nALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'total_excluding_tax\')::bigint) STORED;\n\n-- customers\nALTER TABLE "stripe"."customers" DROP COLUMN "balance";\nALTER TABLE "stripe"."customers" ADD COLUMN "balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'balance\')::bigint) STORED;\n\n-- invoices\nALTER TABLE "stripe"."invoices" DROP COLUMN "ending_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'ending_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "starting_balance";\nALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" bigint GENERATED ALWAYS AS ((_raw_data->>\'starting_balance\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "subtotal";\nALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>\'subtotal\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "tax";\nALTER TABLE "stripe"."invoices" ADD COLUMN "tax" bigint GENERATED ALWAYS AS ((_raw_data->>\'tax\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "post_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'post_payment_credit_notes_amount\')::bigint) STORED;\nALTER TABLE "stripe"."invoices" DROP COLUMN "pre_payment_credit_notes_amount";\nALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'pre_payment_credit_notes_amount\')::bigint) STORED;\n\n-- payment_intents\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_capturable";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_capturable\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_received";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount_received\')::bigint) STORED;\nALTER TABLE "stripe"."payment_intents" DROP COLUMN "application_fee_amount";\nALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'application_fee_amount\')::bigint) STORED;\n\n-- prices\nALTER TABLE "stripe"."prices" DROP COLUMN "unit_amount";\nALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'unit_amount\')::bigint) STORED;\n\n-- refunds\nALTER TABLE "stripe"."refunds" DROP COLUMN "amount";\nALTER TABLE "stripe"."refunds" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>\'amount\')::bigint) STORED;\n', - }, - { - name: '0056_sync_run_closed_at.sql', - sql: '-- Add closed_at column to _sync_run\n-- closed_at IS NULL means the run is still active\n-- Status is derived from object states when closed_at IS NOT NULL\n\n-- Step 1: Drop dependent view first\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Drop the old constraint, status column, and completed_at column\nALTER TABLE "stripe"."_sync_run" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS status;\nALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS completed_at;\n\n-- Step 3: Add closed_at column\nALTER TABLE "stripe"."_sync_run" ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;\n\n-- Step 4: Create exclusion constraint (only one active run per account)\nALTER TABLE "stripe"."_sync_run"\nADD CONSTRAINT one_active_run_per_account\nEXCLUDE ("_account_id" WITH =) WHERE (closed_at IS NULL);\n\n-- Step 5: Recreate sync_dashboard view (run-level only, one row per run)\n-- Base table: _sync_run (parent sync operation)\n-- Child table: _sync_obj_run (individual object syncs)\nCREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS\nSELECT\n run."_account_id" as account_id,\n run.started_at,\n run.closed_at,\n run.max_concurrent,\n run.triggered_by,\n run.updated_at,\n -- Derived status from object states\n CASE\n WHEN run.closed_at IS NULL THEN \'running\'\n WHEN EXISTS (\n SELECT 1 FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ) THEN \'error\'\n ELSE \'complete\'\n END as status,\n -- First error message from failed objects\n (SELECT obj.error_message FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at\n AND obj.status = \'error\'\n ORDER BY obj.object LIMIT 1) as error_message,\n -- Total processed count across all objects\n COALESCE((SELECT SUM(obj.processed_count) FROM "stripe"."_sync_obj_run" obj\n WHERE obj."_account_id" = run."_account_id"\n AND obj.run_started_at = run.started_at), 0) as processed_count\nFROM "stripe"."_sync_run" run;\n', - }, - { - name: '0057_rename_sync_tables.sql', - sql: '-- Rename sync observability tables and create public sync_runs view\n-- Internal tables use _ prefix, public view is sync_runs\n\n-- Step 1: Drop the old sync_dashboard view\nDROP VIEW IF EXISTS "stripe"."sync_dashboard";\n\n-- Step 2: Rename tables to plural (keep _ prefix for internal tables)\nALTER TABLE "stripe"."_sync_run" RENAME TO "_sync_runs";\nALTER TABLE "stripe"."_sync_obj_run" RENAME TO "_sync_obj_runs";\n\n-- Step 3: Update foreign key constraint name\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS fk_sync_obj_run_parent;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT fk_sync_obj_runs_parent\n FOREIGN KEY ("_account_id", run_started_at)\n REFERENCES "stripe"."_sync_runs" ("_account_id", started_at);\n\n-- Step 4: Recreate indexes with new table names\nDROP INDEX IF EXISTS "stripe"."idx_sync_run_account_status";\nDROP INDEX IF EXISTS "stripe"."idx_sync_obj_run_status";\n\nCREATE INDEX idx_sync_runs_account_status\n ON "stripe"."_sync_runs" ("_account_id", closed_at);\n\nCREATE INDEX idx_sync_obj_runs_status\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status);\n\n-- Step 5: Create public sync_runs view (one row per run with aggregates)\nCREATE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n', - }, - { - name: '0058_improve_sync_runs_status.sql', - sql: "-- Improve sync_runs view status logic\n-- More granular status based on actual object run states\n\nDROP VIEW IF EXISTS \"stripe\".\"sync_runs\";\n\nCREATE VIEW \"stripe\".\"sync_runs\" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n -- Aggregate metrics from child objects\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count,\n COUNT(*) FILTER (WHERE o.status = 'error') as error_count,\n COUNT(*) FILTER (WHERE o.status = 'running') as running_count,\n COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count,\n -- Collect error messages if any\n STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n -- Derive overall status from run state and object states\n CASE\n -- Run still open (closed_at IS NULL)\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending'\n WHEN r.closed_at IS NULL THEN 'running'\n -- Run closed (closed_at IS NOT NULL)\n WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error'\n ELSE 'complete'\n END as status\nFROM \"stripe\".\"_sync_runs\" r\nLEFT JOIN \"stripe\".\"_sync_obj_runs\" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n", - }, - { - name: '0059_sigma_subscription_item_change_events_v2_beta.sql', - sql: '-- event_timestamp and event_type are not generated columns because they are not immutable. \n-- Postgres requires generated expressions to be immutable.\n\nCREATE TABLE IF NOT EXISTS "stripe"."subscription_item_change_events_v2_beta" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "event_timestamp" timestamptz NOT NULL,\n "event_type" text NOT NULL,\n "subscription_item_id" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "event_timestamp", "event_type", "subscription_item_id")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n DROP CONSTRAINT IF EXISTS fk_subscription_item_change_events_v2_beta_account;\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD CONSTRAINT fk_subscription_item_change_events_v2_beta_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."subscription_item_change_events_v2_beta";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."subscription_item_change_events_v2_beta"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "currency" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'currency\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "mrr_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'mrr_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "quantity_change" bigint\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'quantity_change\', \'\'))::bigint) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "subscription_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'subscription_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "customer_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'customer_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "price_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'price_id\', \'\'))::text) STORED;\n\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "product_id" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'product_id\', \'\'))::text) STORED;\n\n-- Keep as text to avoid non-immutable timestamp casts in a generated column\nALTER TABLE "stripe"."subscription_item_change_events_v2_beta"\n ADD COLUMN IF NOT EXISTS "local_event_timestamp" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'local_event_timestamp\', \'\'))::text) STORED;', - }, - { - name: '0060_sigma_exchange_rates_from_usd.sql', - sql: '\nCREATE TABLE IF NOT EXISTS "stripe"."exchange_rates_from_usd" (\n "_raw_data" jsonb NOT NULL,\n "_last_synced_at" timestamptz,\n "_updated_at" timestamptz DEFAULT now(),\n "_account_id" text NOT NULL,\n\n "date" date NOT NULL,\n "sell_currency" text NOT NULL,\n\n PRIMARY KEY ("_account_id", "date", "sell_currency")\n);\n\n-- Foreign key to stripe.accounts\nALTER TABLE "stripe"."exchange_rates_from_usd"\n DROP CONSTRAINT IF EXISTS fk_exchange_rates_from_usd_account;\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD CONSTRAINT fk_exchange_rates_from_usd_account\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\n\n-- Maintain _updated_at on UPDATE\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."exchange_rates_from_usd";\nCREATE TRIGGER handle_updated_at\n BEFORE UPDATE ON "stripe"."exchange_rates_from_usd"\n FOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nALTER TABLE "stripe"."exchange_rates_from_usd"\n ADD COLUMN IF NOT EXISTS "buy_currency_exchange_rates" text\n GENERATED ALWAYS AS ((NULLIF(_raw_data->>\'buy_currency_exchange_rates\', \'\'))::text) STORED;\n\n-- Index on date for efficient range queries\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_date\n ON "stripe"."exchange_rates_from_usd" ("date");\n\n-- Index on sell_currency for filtering by currency\nCREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_sell_currency\n ON "stripe"."exchange_rates_from_usd" ("sell_currency");\n\n', - }, - { - name: '0061_add_page_cursor.sql', - sql: '-- Add page_cursor column for pagination state within a single sync run.\n-- This is used to store the starting_after ID for backfills using Stripe list calls.\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS page_cursor text;\n', - }, - { - name: '0062_sigma_query_runs.sql', - sql: '-- Allow parallel sync runs per triggered_by (sigma-worker vs stripe-worker)\nALTER TABLE "stripe"."_sync_runs" DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\nADD CONSTRAINT one_active_run_per_account_triggered_by\nEXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n) WHERE (closed_at IS NULL);\n', - }, - { - name: '0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql', - sql: '-- Drop unused sigma beta tables if they exist.\nDROP TABLE IF EXISTS "stripe"."subscription_item_change_events_v2_beta";\nDROP TABLE IF EXISTS "stripe"."exchange_rates_from_usd";\n', - }, - { - name: '0064_add_created_gte_lte.sql', - sql: '-- Add created_gte / created_lte columns for time-range partitioned parallel sync.\n-- Workers use these to scope their Stripe list calls to a specific created window.\n-- Stored as Unix epoch seconds (INTEGER) to match Stripe\'s created filter format.\n-- created_gte defaults to 0 for non-chunked rows (required by PK).\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_gte INTEGER NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_lte INTEGER;\n\n-- Expand PK to include created_gte so multiple time-range chunks of the same object can coexist.\n-- PK constraint kept original name from 0053 (_sync_obj_run_pkey) after table rename in 0057.\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_run_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte);\n', - }, - { - name: '0065_add_created_lte_to_pk.sql', - sql: '-- Include created_lte in the PK so chunks with the same created_gte but\n-- different created_lte can coexist. Requires a NOT NULL default first.\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET DEFAULT 0;\n\nUPDATE "stripe"."_sync_obj_runs"\n SET created_lte = 0\n WHERE created_lte IS NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ALTER COLUMN created_lte SET NOT NULL;\n\nALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte, created_lte);\n', - }, - { - name: '0066_rate_limits.sql', - sql: '-- Rate limiting table and function for cross-process request throttling.\n-- Used by claimNextTask to cap how many claims/sec hit the database.\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n', - }, - { - name: '0067_add_priority_to_sync_obj_runs.sql', - sql: '-- Add priority column to _sync_obj_runs for deterministic task ordering.\n-- Priority mirrors the `order` field from resourceRegistry so workers\n-- always process parent resources (products, prices) before children\n-- (subscriptions, invoices).\n\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS idx_sync_obj_runs_priority\n ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status, priority);\n', - }, - { - name: '0068_sync_obj_progress_view.sql', - sql: '-- Per-object sync progress view for monitoring.\n-- Defaults to the newest run per account; callers can filter by a specific\n-- run_started_at if needed.\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\n\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', - }, - { - name: '0069_internal_sync_schema.sql', sql: '-- Internal sync metadata schema bootstrap for OpenAPI runtime.\n-- Uses idempotent DDL so it can be safely re-run.\n\nCREATE EXTENSION IF NOT EXISTS btree_gist;\n\nCREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n -- Support both legacy "updated_at" and newer "_updated_at" columns.\n -- jsonb_populate_record silently ignores keys that are not present on NEW.\n NEW := jsonb_populate_record(\n NEW,\n jsonb_build_object(\n \'updated_at\', now(),\n \'_updated_at\', now()\n )\n );\n RETURN NEW;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger\n LANGUAGE plpgsql\nAS $$\nBEGIN\n NEW.updated_at = now();\n RETURN NEW;\nEND;\n$$;\n\nCREATE TABLE IF NOT EXISTS "stripe"."accounts" (\n "_raw_data" jsonb NOT NULL,\n "id" text GENERATED ALWAYS AS ((_raw_data->>\'id\')::text) STORED,\n "api_key_hashes" text[] NOT NULL DEFAULT \'{}\',\n "first_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_last_synced_at" timestamptz NOT NULL DEFAULT now(),\n "_updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("id")\n);\nCREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes"\n ON "stripe"."accounts" USING GIN ("api_key_hashes");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."accounts"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" (\n "id" text PRIMARY KEY,\n "object" text,\n "url" text NOT NULL,\n "enabled_events" jsonb NOT NULL,\n "description" text,\n "enabled" boolean,\n "livemode" boolean,\n "metadata" jsonb,\n "secret" text NOT NULL,\n "status" text,\n "api_version" text,\n "created" bigint,\n "last_synced_at" timestamptz,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n "account_id" text NOT NULL\n);\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id");\nALTER TABLE "stripe"."_managed_webhooks"\n DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account";\nALTER TABLE "stripe"."_managed_webhooks"\n ADD CONSTRAINT "fk_managed_webhooks_account"\n FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status"\n ON "stripe"."_managed_webhooks" ("status");\nCREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled"\n ON "stripe"."_managed_webhooks" ("enabled");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_managed_webhooks"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" (\n "_account_id" text NOT NULL,\n "started_at" timestamptz NOT NULL DEFAULT now(),\n "closed_at" timestamptz,\n "max_concurrent" integer NOT NULL DEFAULT 3,\n "triggered_by" text,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "started_at")\n);\nALTER TABLE "stripe"."_sync_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_runs_account";\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT "fk_sync_runs_account"\n FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account;\nALTER TABLE "stripe"."_sync_runs"\n DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by;\nALTER TABLE "stripe"."_sync_runs"\n ADD CONSTRAINT one_active_run_per_account_triggered_by\n EXCLUDE (\n "_account_id" WITH =,\n COALESCE(triggered_by, \'default\') WITH =\n ) WHERE (closed_at IS NULL);\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status"\n ON "stripe"."_sync_runs" ("_account_id", "closed_at");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" (\n "_account_id" text NOT NULL,\n "run_started_at" timestamptz NOT NULL,\n "object" text NOT NULL,\n "status" text NOT NULL DEFAULT \'pending\'\n CHECK (status IN (\'pending\', \'running\', \'complete\', \'error\')),\n "started_at" timestamptz,\n "completed_at" timestamptz,\n "processed_count" integer NOT NULL DEFAULT 0,\n "cursor" text,\n "page_cursor" text,\n "created_gte" integer NOT NULL DEFAULT 0,\n "created_lte" integer NOT NULL DEFAULT 0,\n "priority" integer NOT NULL DEFAULT 0,\n "error_message" text,\n "updated_at" timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte")\n);\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "page_cursor" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0;\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD COLUMN IF NOT EXISTS "error_message" text;\nALTER TABLE "stripe"."_sync_obj_runs"\n DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent";\nALTER TABLE "stripe"."_sync_obj_runs"\n ADD CONSTRAINT "fk_sync_obj_runs_parent"\n FOREIGN KEY ("_account_id", "run_started_at")\n REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at");\nDROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs";\nCREATE TRIGGER handle_updated_at\nBEFORE UPDATE ON "stripe"."_sync_obj_runs"\nFOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata();\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status");\nCREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority"\n ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority");\n\nCREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n window_start TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION "stripe".check_rate_limit(\n rate_key TEXT,\n max_requests INTEGER,\n window_seconds INTEGER\n)\nRETURNS VOID AS $$\nDECLARE\n now TIMESTAMPTZ := clock_timestamp();\n window_length INTERVAL := make_interval(secs => window_seconds);\n current_count INTEGER;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext(rate_key));\n\n INSERT INTO "stripe"."_rate_limits" (key, count, window_start)\n VALUES (rate_key, 1, now)\n ON CONFLICT (key) DO UPDATE\n SET count = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN 1\n ELSE "_rate_limits".count + 1\n END,\n window_start = CASE\n WHEN "_rate_limits".window_start + window_length <= now\n THEN now\n ELSE "_rate_limits".window_start\n END;\n\n SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;\n\n IF current_count > max_requests THEN\n RAISE EXCEPTION \'Rate limit exceeded for %\', rate_key;\n END IF;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE OR REPLACE VIEW "stripe"."sync_runs" AS\nSELECT\n r._account_id as account_id,\n r.started_at,\n r.closed_at,\n r.triggered_by,\n r.max_concurrent,\n COALESCE(SUM(o.processed_count), 0) as total_processed,\n COUNT(o.*) as total_objects,\n COUNT(*) FILTER (WHERE o.status = \'complete\') as complete_count,\n COUNT(*) FILTER (WHERE o.status = \'error\') as error_count,\n COUNT(*) FILTER (WHERE o.status = \'running\') as running_count,\n COUNT(*) FILTER (WHERE o.status = \'pending\') as pending_count,\n STRING_AGG(o.error_message, \'; \') FILTER (WHERE o.error_message IS NOT NULL) as error_message,\n CASE\n WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = \'running\') > 0 THEN \'running\'\n WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = \'pending\')) THEN \'pending\'\n WHEN r.closed_at IS NULL THEN \'running\'\n WHEN COUNT(*) FILTER (WHERE o.status = \'error\') > 0 THEN \'error\'\n ELSE \'complete\'\n END as status\nFROM "stripe"."_sync_runs" r\nLEFT JOIN "stripe"."_sync_obj_runs" o\n ON o._account_id = r._account_id\n AND o.run_started_at = r.started_at\nGROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent;\n\nDROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);\nCREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS\nSELECT\n r."_account_id" AS account_id,\n r.run_started_at,\n r.object,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE r.status = \'complete\') / NULLIF(COUNT(*), 0),\n 1\n ) AS pct_complete,\n COALESCE(SUM(r.processed_count), 0) AS processed\nFROM "stripe"."_sync_obj_runs" r\nWHERE r.run_started_at = (\n SELECT MAX(s.started_at)\n FROM "stripe"."_sync_runs" s\n WHERE s."_account_id" = r."_account_id"\n)\nGROUP BY r."_account_id", r.run_started_at, r.object;\n', }, ] diff --git a/packages/sync-engine/src/database/migrations/0000_initial_migration.sql b/packages/sync-engine/src/database/migrations/0000_initial_migration.sql index 9e13a3ef..7efd2d50 100644 --- a/packages/sync-engine/src/database/migrations/0000_initial_migration.sql +++ b/packages/sync-engine/src/database/migrations/0000_initial_migration.sql @@ -1 +1,245 @@ -select 1; \ No newline at end of file +-- Internal sync metadata schema bootstrap for OpenAPI runtime. +-- Uses idempotent DDL so it can be safely re-run. + +CREATE EXTENSION IF NOT EXISTS btree_gist; + +CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + -- Support both legacy "updated_at" and newer "_updated_at" columns. + -- jsonb_populate_record silently ignores keys that are not present on NEW. + NEW := jsonb_populate_record( + NEW, + jsonb_build_object( + 'updated_at', now(), + '_updated_at', now() + ) + ); + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + +CREATE TABLE IF NOT EXISTS "stripe"."accounts" ( + "_raw_data" jsonb NOT NULL, + "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, + "api_key_hashes" text[] NOT NULL DEFAULT '{}', + "first_synced_at" timestamptz NOT NULL DEFAULT now(), + "_last_synced_at" timestamptz NOT NULL DEFAULT now(), + "_updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +CREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes" + ON "stripe"."accounts" USING GIN ("api_key_hashes"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."accounts" +FOR EACH ROW EXECUTE FUNCTION set_updated_at(); + +CREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" ( + "id" text PRIMARY KEY, + "object" text, + "url" text NOT NULL, + "enabled_events" jsonb NOT NULL, + "description" text, + "enabled" boolean, + "livemode" boolean, + "metadata" jsonb, + "secret" text NOT NULL, + "status" text, + "api_version" text, + "created" bigint, + "last_synced_at" timestamptz, + "updated_at" timestamptz NOT NULL DEFAULT now(), + "account_id" text NOT NULL +); +ALTER TABLE "stripe"."_managed_webhooks" + DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique"; +ALTER TABLE "stripe"."_managed_webhooks" + ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id"); +ALTER TABLE "stripe"."_managed_webhooks" + DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account"; +ALTER TABLE "stripe"."_managed_webhooks" + ADD CONSTRAINT "fk_managed_webhooks_account" + FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id); +CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status" + ON "stripe"."_managed_webhooks" ("status"); +CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled" + ON "stripe"."_managed_webhooks" ("enabled"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_managed_webhooks" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); + +CREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" ( + "_account_id" text NOT NULL, + "started_at" timestamptz NOT NULL DEFAULT now(), + "closed_at" timestamptz, + "max_concurrent" integer NOT NULL DEFAULT 3, + "triggered_by" text, + "error_message" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "started_at") +); +ALTER TABLE "stripe"."_sync_runs" + ADD COLUMN IF NOT EXISTS "error_message" text; +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS "fk_sync_runs_account"; +ALTER TABLE "stripe"."_sync_runs" + ADD CONSTRAINT "fk_sync_runs_account" + FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS one_active_run_per_account; +ALTER TABLE "stripe"."_sync_runs" + DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by; +ALTER TABLE "stripe"."_sync_runs" + ADD CONSTRAINT one_active_run_per_account_triggered_by + EXCLUDE ( + "_account_id" WITH =, + COALESCE(triggered_by, 'default') WITH = + ) WHERE (closed_at IS NULL); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_sync_runs" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); +CREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status" + ON "stripe"."_sync_runs" ("_account_id", "closed_at"); + +CREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" ( + "_account_id" text NOT NULL, + "run_started_at" timestamptz NOT NULL, + "object" text NOT NULL, + "status" text NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'running', 'complete', 'error')), + "started_at" timestamptz, + "completed_at" timestamptz, + "processed_count" integer NOT NULL DEFAULT 0, + "cursor" text, + "page_cursor" text, + "created_gte" integer NOT NULL DEFAULT 0, + "created_lte" integer NOT NULL DEFAULT 0, + "priority" integer NOT NULL DEFAULT 0, + "error_message" text, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte") +); +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "page_cursor" text; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD COLUMN IF NOT EXISTS "error_message" text; +ALTER TABLE "stripe"."_sync_obj_runs" + DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent"; +ALTER TABLE "stripe"."_sync_obj_runs" + ADD CONSTRAINT "fk_sync_obj_runs_parent" + FOREIGN KEY ("_account_id", "run_started_at") + REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at"); +DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs"; +CREATE TRIGGER handle_updated_at +BEFORE UPDATE ON "stripe"."_sync_obj_runs" +FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); +CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); +CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority" + ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority"); + +CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( + key TEXT PRIMARY KEY, + count INTEGER NOT NULL DEFAULT 0, + window_start TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( + rate_key TEXT, + max_requests INTEGER, + window_seconds INTEGER +) +RETURNS VOID AS $$ +DECLARE + now TIMESTAMPTZ := clock_timestamp(); + window_length INTERVAL := make_interval(secs => window_seconds); + current_count INTEGER; +BEGIN + PERFORM pg_advisory_xact_lock(hashtext(rate_key)); + + INSERT INTO "stripe"."_rate_limits" (key, count, window_start) + VALUES (rate_key, 1, now) + ON CONFLICT (key) DO UPDATE + SET count = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN 1 + ELSE "_rate_limits".count + 1 + END, + window_start = CASE + WHEN "_rate_limits".window_start + window_length <= now + THEN now + ELSE "_rate_limits".window_start + END; + + SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; + + IF current_count > max_requests THEN + RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE VIEW "stripe"."sync_runs" AS +SELECT + r._account_id as account_id, + r.started_at, + r.closed_at, + r.triggered_by, + r.max_concurrent, + COALESCE(SUM(o.processed_count), 0) as total_processed, + COUNT(o.*) as total_objects, + COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, + COUNT(*) FILTER (WHERE o.status = 'error') as error_count, + COUNT(*) FILTER (WHERE o.status = 'running') as running_count, + COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, + STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, + CASE + WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' + WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' + WHEN r.closed_at IS NULL THEN 'running' + WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' + ELSE 'complete' + END as status +FROM "stripe"."_sync_runs" r +LEFT JOIN "stripe"."_sync_obj_runs" o + ON o._account_id = r._account_id + AND o.run_started_at = r.started_at +GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; + +DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ); +CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS +SELECT + r."_account_id" AS account_id, + r.run_started_at, + r.object, + ROUND( + 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), + 1 + ) AS pct_complete, + COALESCE(SUM(r.processed_count), 0) AS processed +FROM "stripe"."_sync_obj_runs" r +WHERE r.run_started_at = ( + SELECT MAX(s.started_at) + FROM "stripe"."_sync_runs" s + WHERE s."_account_id" = r."_account_id" +) +GROUP BY r."_account_id", r.run_started_at, r.object; diff --git a/packages/sync-engine/src/database/migrations/0001_products.sql b/packages/sync-engine/src/database/migrations/0001_products.sql deleted file mode 100644 index 49ee08a7..00000000 --- a/packages/sync-engine/src/database/migrations/0001_products.sql +++ /dev/null @@ -1,17 +0,0 @@ -create table if not exists "stripe"."products" ( - "id" text primary key, - "object" text, - "active" boolean, - "description" text, - "metadata" jsonb, - "name" text, - "created" integer, - "images" jsonb, - "livemode" boolean, - "package_dimensions" jsonb, - "shippable" boolean, - "statement_descriptor" text, - "unit_label" text, - "updated" integer, - "url" text -); diff --git a/packages/sync-engine/src/database/migrations/0002_customers.sql b/packages/sync-engine/src/database/migrations/0002_customers.sql deleted file mode 100644 index c8132f0e..00000000 --- a/packages/sync-engine/src/database/migrations/0002_customers.sql +++ /dev/null @@ -1,23 +0,0 @@ -create table if not exists "stripe"."customers" ( - "id" text primary key, - "object" text, - "address" jsonb, - "description" text, - "email" text, - "metadata" jsonb, - "name" text, - "phone" text, - "shipping" jsonb, - "balance" integer, - "created" integer, - "currency" text, - "default_source" text, - "delinquent" boolean, - "discount" jsonb, - "invoice_prefix" text, - "invoice_settings" jsonb, - "livemode" boolean, - "next_invoice_sequence" integer, - "preferred_locales" jsonb, - "tax_exempt" text -); diff --git a/packages/sync-engine/src/database/migrations/0003_prices.sql b/packages/sync-engine/src/database/migrations/0003_prices.sql deleted file mode 100644 index e133637a..00000000 --- a/packages/sync-engine/src/database/migrations/0003_prices.sql +++ /dev/null @@ -1,34 +0,0 @@ -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'pricing_type') THEN - create type "stripe"."pricing_type" as enum ('one_time', 'recurring'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'pricing_tiers') THEN - create type "stripe"."pricing_tiers" as enum ('graduated', 'volume'); - END IF; - --more types here... -END -$$; - - -create table if not exists "stripe"."prices" ( - "id" text primary key, - "object" text, - "active" boolean, - "currency" text, - "metadata" jsonb, - "nickname" text, - "recurring" jsonb, - "type" stripe.pricing_type, - "unit_amount" integer, - "billing_scheme" text, - "created" integer, - "livemode" boolean, - "lookup_key" text, - "tiers_mode" stripe.pricing_tiers, - "transform_quantity" jsonb, - "unit_amount_decimal" text, - - "product" text references stripe.products -); - diff --git a/packages/sync-engine/src/database/migrations/0004_subscriptions.sql b/packages/sync-engine/src/database/migrations/0004_subscriptions.sql deleted file mode 100644 index bd9e5753..00000000 --- a/packages/sync-engine/src/database/migrations/0004_subscriptions.sql +++ /dev/null @@ -1,56 +0,0 @@ - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'subscription_status') THEN - create type "stripe"."subscription_status" as enum ( - 'trialing', - 'active', - 'canceled', - 'incomplete', - 'incomplete_expired', - 'past_due', - 'unpaid' - ); - END IF; -END -$$; - -create table if not exists "stripe"."subscriptions" ( - "id" text primary key, - "object" text, - "cancel_at_period_end" boolean, - "current_period_end" integer, - "current_period_start" integer, - "default_payment_method" text, - "items" jsonb, - "metadata" jsonb, - "pending_setup_intent" text, - "pending_update" jsonb, - "status" "stripe"."subscription_status", - "application_fee_percent" double precision, - "billing_cycle_anchor" integer, - "billing_thresholds" jsonb, - "cancel_at" integer, - "canceled_at" integer, - "collection_method" text, - "created" integer, - "days_until_due" integer, - "default_source" text, - "default_tax_rates" jsonb, - "discount" jsonb, - "ended_at" integer, - "livemode" boolean, - "next_pending_invoice_item_invoice" integer, - "pause_collection" jsonb, - "pending_invoice_item_interval" jsonb, - "start_date" integer, - "transfer_data" jsonb, - "trial_end" jsonb, - "trial_start" jsonb, - - "schedule" text, - "customer" text references "stripe"."customers", - "latest_invoice" text, -- not yet joined - "plan" text -- not yet joined -); - diff --git a/packages/sync-engine/src/database/migrations/0005_invoices.sql b/packages/sync-engine/src/database/migrations/0005_invoices.sql deleted file mode 100644 index 0b664eda..00000000 --- a/packages/sync-engine/src/database/migrations/0005_invoices.sql +++ /dev/null @@ -1,77 +0,0 @@ - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'invoice_status') THEN - create type "stripe"."invoice_status" as enum ('draft', 'open', 'paid', 'uncollectible', 'void'); - END IF; -END -$$; - - -create table if not exists "stripe"."invoices" ( - "id" text primary key, - "object" text, - "auto_advance" boolean, - "collection_method" text, - "currency" text, - "description" text, - "hosted_invoice_url" text, - "lines" jsonb, - "metadata" jsonb, - "period_end" integer, - "period_start" integer, - "status" "stripe"."invoice_status", - "total" bigint, - "account_country" text, - "account_name" text, - "account_tax_ids" jsonb, - "amount_due" bigint, - "amount_paid" bigint, - "amount_remaining" bigint, - "application_fee_amount" bigint, - "attempt_count" integer, - "attempted" boolean, - "billing_reason" text, - "created" integer, - "custom_fields" jsonb, - "customer_address" jsonb, - "customer_email" text, - "customer_name" text, - "customer_phone" text, - "customer_shipping" jsonb, - "customer_tax_exempt" text, - "customer_tax_ids" jsonb, - "default_tax_rates" jsonb, - "discount" jsonb, - "discounts" jsonb, - "due_date" integer, - "ending_balance" integer, - "footer" text, - "invoice_pdf" text, - "last_finalization_error" jsonb, - "livemode" boolean, - "next_payment_attempt" integer, - "number" text, - "paid" boolean, - "payment_settings" jsonb, - "post_payment_credit_notes_amount" integer, - "pre_payment_credit_notes_amount" integer, - "receipt_number" text, - "starting_balance" integer, - "statement_descriptor" text, - "status_transitions" jsonb, - "subtotal" integer, - "tax" integer, - "total_discount_amounts" jsonb, - "total_tax_amounts" jsonb, - "transfer_data" jsonb, - "webhooks_delivered_at" integer, - - "customer" text references "stripe"."customers", - "subscription" text references "stripe"."subscriptions", - "payment_intent" text, -- not yet implemented - "default_payment_method" text, -- not yet implemented - "default_source" text, -- not yet implemented - "on_behalf_of" text, -- not yet implemented - "charge" text -- not yet implemented -); diff --git a/packages/sync-engine/src/database/migrations/0006_charges.sql b/packages/sync-engine/src/database/migrations/0006_charges.sql deleted file mode 100644 index c364a6c7..00000000 --- a/packages/sync-engine/src/database/migrations/0006_charges.sql +++ /dev/null @@ -1,43 +0,0 @@ - -create table if not exists "stripe".charges ( - id text primary key, - object text, - card jsonb, - paid boolean, - "order" text, - amount bigint, - review text, - source jsonb, - status text, - created integer, - dispute text, - invoice text, - outcome jsonb, - refunds jsonb, - updated integer, - captured boolean, - currency text, - customer text, - livemode boolean, - metadata jsonb, - refunded boolean, - shipping jsonb, - application text, - description text, - destination text, - failure_code text, - on_behalf_of text, - fraud_details jsonb, - receipt_email text, - payment_intent text, - receipt_number text, - transfer_group text, - amount_refunded bigint, - application_fee text, - failure_message text, - source_transfer text, - balance_transaction text, - statement_descriptor text, - statement_description text, - payment_method_details jsonb -); diff --git a/packages/sync-engine/src/database/migrations/0007_coupons.sql b/packages/sync-engine/src/database/migrations/0007_coupons.sql deleted file mode 100644 index 78a90d3a..00000000 --- a/packages/sync-engine/src/database/migrations/0007_coupons.sql +++ /dev/null @@ -1,19 +0,0 @@ -create table if not exists "stripe".coupons ( - id text primary key, - object text, - name text, - valid boolean, - created integer, - updated integer, - currency text, - duration text, - livemode boolean, - metadata jsonb, - redeem_by integer, - amount_off bigint, - percent_off double precision, - times_redeemed bigint, - max_redemptions bigint, - duration_in_months bigint, - percent_off_precise double precision -); diff --git a/packages/sync-engine/src/database/migrations/0008_disputes.sql b/packages/sync-engine/src/database/migrations/0008_disputes.sql deleted file mode 100644 index 6d2e38e0..00000000 --- a/packages/sync-engine/src/database/migrations/0008_disputes.sql +++ /dev/null @@ -1,17 +0,0 @@ -create table if not exists "stripe".disputes ( - id text primary key, - object text, - amount bigint, - charge text, - reason text, - status text, - created integer, - updated integer, - currency text, - evidence jsonb, - livemode boolean, - metadata jsonb, - evidence_details jsonb, - balance_transactions jsonb, - is_charge_refundable boolean -); diff --git a/packages/sync-engine/src/database/migrations/0009_events.sql b/packages/sync-engine/src/database/migrations/0009_events.sql deleted file mode 100644 index bdc984fb..00000000 --- a/packages/sync-engine/src/database/migrations/0009_events.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table if not exists "stripe".events ( - id text primary key, - object text, - data jsonb, - type text, - created integer, - request text, - updated integer, - livemode boolean, - api_version text, - pending_webhooks bigint -); diff --git a/packages/sync-engine/src/database/migrations/0010_payouts.sql b/packages/sync-engine/src/database/migrations/0010_payouts.sql deleted file mode 100644 index 6bf4e599..00000000 --- a/packages/sync-engine/src/database/migrations/0010_payouts.sql +++ /dev/null @@ -1,30 +0,0 @@ -create table if not exists "stripe".payouts ( - id text primary key, - object text, - date text, - type text, - amount bigint, - method text, - status text, - created integer, - updated integer, - currency text, - livemode boolean, - metadata jsonb, - automatic boolean, - recipient text, - description text, - destination text, - source_type text, - arrival_date text, - bank_account jsonb, - failure_code text, - transfer_group text, - amount_reversed bigint, - failure_message text, - source_transaction text, - balance_transaction text, - statement_descriptor text, - statement_description text, - failure_balance_transaction text -); diff --git a/packages/sync-engine/src/database/migrations/0011_plans.sql b/packages/sync-engine/src/database/migrations/0011_plans.sql deleted file mode 100644 index 9551c44d..00000000 --- a/packages/sync-engine/src/database/migrations/0011_plans.sql +++ /dev/null @@ -1,25 +0,0 @@ -create table if not exists "stripe"."plans" ( - id text primary key, - object text, - name text, - tiers jsonb, - active boolean, - amount bigint, - created integer, - product text, - updated integer, - currency text, - "interval" text, - livemode boolean, - metadata jsonb, - nickname text, - tiers_mode text, - usage_type text, - billing_scheme text, - interval_count bigint, - aggregate_usage text, - transform_usage text, - trial_period_days bigint, - statement_descriptor text, - statement_description text -); diff --git a/packages/sync-engine/src/database/migrations/0012_add_updated_at.sql b/packages/sync-engine/src/database/migrations/0012_add_updated_at.sql deleted file mode 100644 index c1186a68..00000000 --- a/packages/sync-engine/src/database/migrations/0012_add_updated_at.sql +++ /dev/null @@ -1,108 +0,0 @@ -create or replace function set_updated_at() returns trigger - language plpgsql -as -$$ -begin - new.updated_at = now(); - return NEW; -end; -$$; - -alter table stripe.subscriptions - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.subscriptions - for each row - execute procedure set_updated_at(); - -alter table stripe.products - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.products - for each row - execute procedure set_updated_at(); - -alter table stripe.customers - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.customers - for each row - execute procedure set_updated_at(); - -alter table stripe.prices - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.prices - for each row - execute procedure set_updated_at(); - -alter table stripe.invoices - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.invoices - for each row - execute procedure set_updated_at(); - -alter table stripe.charges - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.charges - for each row - execute procedure set_updated_at(); - -alter table stripe.coupons - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.coupons - for each row - execute procedure set_updated_at(); - -alter table stripe.disputes - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.disputes - for each row - execute procedure set_updated_at(); - -alter table stripe.events - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.events - for each row - execute procedure set_updated_at(); - -alter table stripe.payouts - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.payouts - for each row - execute procedure set_updated_at(); - -alter table stripe.plans - add updated_at timestamptz default timezone('utc'::text, now()) not null; - -create trigger handle_updated_at - before update - on stripe.plans - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0013_add_subscription_items.sql b/packages/sync-engine/src/database/migrations/0013_add_subscription_items.sql deleted file mode 100644 index ff1c40b7..00000000 --- a/packages/sync-engine/src/database/migrations/0013_add_subscription_items.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table if not exists "stripe"."subscription_items" ( - "id" text primary key, - "object" text, - "billing_thresholds" jsonb, - "created" integer, - "deleted" boolean, - "metadata" jsonb, - "quantity" integer, - "price" text references "stripe"."prices", - "subscription" text references "stripe"."subscriptions", - "tax_rates" jsonb -); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0014_migrate_subscription_items.sql b/packages/sync-engine/src/database/migrations/0014_migrate_subscription_items.sql deleted file mode 100644 index c593957a..00000000 --- a/packages/sync-engine/src/database/migrations/0014_migrate_subscription_items.sql +++ /dev/null @@ -1,26 +0,0 @@ -WITH subscriptions AS ( - select jsonb_array_elements(items->'data') as obj from "stripe"."subscriptions" -) -insert into "stripe"."subscription_items" -select obj->>'id' as "id", - obj->>'object' as "object", - obj->'billing_thresholds' as "billing_thresholds", - (obj->>'created')::INTEGER as "created", - (obj->>'deleted')::BOOLEAN as "deleted", - obj->'metadata' as "metadata", - (obj->>'quantity')::INTEGER as "quantity", - (obj->'price'->>'id')::TEXT as "price", - obj->>'subscription' as "subscription", - obj->'tax_rates' as "tax_rates" -from subscriptions -on conflict ("id") -do update set "id" = excluded."id", - "object" = excluded."object", - "billing_thresholds" = excluded."billing_thresholds", - "created" = excluded."created", - "deleted" = excluded."deleted", - "metadata" = excluded."metadata", - "quantity" = excluded."quantity", - "price" = excluded."price", - "subscription" = excluded."subscription", - "tax_rates" = excluded."tax_rates" \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0015_add_customer_deleted.sql b/packages/sync-engine/src/database/migrations/0015_add_customer_deleted.sql deleted file mode 100644 index 23c0fc3d..00000000 --- a/packages/sync-engine/src/database/migrations/0015_add_customer_deleted.sql +++ /dev/null @@ -1,2 +0,0 @@ -alter table stripe.customers - add deleted boolean default false not null; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0016_add_invoice_indexes.sql b/packages/sync-engine/src/database/migrations/0016_add_invoice_indexes.sql deleted file mode 100644 index a4b8c3a0..00000000 --- a/packages/sync-engine/src/database/migrations/0016_add_invoice_indexes.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer); -CREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0017_drop_charges_unavailable_columns.sql b/packages/sync-engine/src/database/migrations/0017_drop_charges_unavailable_columns.sql deleted file mode 100644 index 4874897e..00000000 --- a/packages/sync-engine/src/database/migrations/0017_drop_charges_unavailable_columns.sql +++ /dev/null @@ -1,6 +0,0 @@ --- drop columns that are duplicated / not available anymore --- card is not available on webhook v.2020-03-02. We can get the detail from payment_method_details --- statement_description is not available on webhook v.2020-03-02 -alter table "stripe"."charges" - drop column if exists "card", - drop column if exists "statement_description"; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0018_setup_intents.sql b/packages/sync-engine/src/database/migrations/0018_setup_intents.sql deleted file mode 100644 index 8a85f8a2..00000000 --- a/packages/sync-engine/src/database/migrations/0018_setup_intents.sql +++ /dev/null @@ -1,17 +0,0 @@ -create table if not exists "stripe"."setup_intents" ( - id text primary key, - object text, - created integer, - customer text, - description text, - payment_method text, - status text, - usage text, - cancellation_reason text, - latest_attempt text, - mandate text, - single_use_mandate text, - on_behalf_of text -); - -CREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0019_payment_methods.sql b/packages/sync-engine/src/database/migrations/0019_payment_methods.sql deleted file mode 100644 index 4040544a..00000000 --- a/packages/sync-engine/src/database/migrations/0019_payment_methods.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table if not exists "stripe"."payment_methods" ( - id text primary key, - object text, - created integer, - customer text, - type text, - billing_details jsonb, - metadata jsonb, - card jsonb -); - -CREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0020_disputes_payment_intent_created_idx.sql b/packages/sync-engine/src/database/migrations/0020_disputes_payment_intent_created_idx.sql deleted file mode 100644 index a6ad6584..00000000 --- a/packages/sync-engine/src/database/migrations/0020_disputes_payment_intent_created_idx.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS payment_intent TEXT; - -CREATE INDEX IF NOT EXISTS stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0021_payment_intent.sql b/packages/sync-engine/src/database/migrations/0021_payment_intent.sql deleted file mode 100644 index a4b8853a..00000000 --- a/packages/sync-engine/src/database/migrations/0021_payment_intent.sql +++ /dev/null @@ -1,42 +0,0 @@ -create table if not exists "stripe"."payment_intents" ( - id text primary key, - object text, - amount integer, - amount_capturable integer, - amount_details jsonb, - amount_received integer, - application text, - application_fee_amount integer, - automatic_payment_methods text, - canceled_at integer, - cancellation_reason text, - capture_method text, - client_secret text, - confirmation_method text, - created integer, - currency text, - customer text, - description text, - invoice text, - last_payment_error text, - livemode boolean, - metadata jsonb, - next_action text, - on_behalf_of text, - payment_method text, - payment_method_options jsonb, - payment_method_types jsonb, - processing text, - receipt_email text, - review text, - setup_future_usage text, - shipping jsonb, - statement_descriptor text, - statement_descriptor_suffix text, - status text, - transfer_data jsonb, - transfer_group text -); - -CREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer); -CREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0022_adjust_plans.sql b/packages/sync-engine/src/database/migrations/0022_adjust_plans.sql deleted file mode 100644 index eb8decbb..00000000 --- a/packages/sync-engine/src/database/migrations/0022_adjust_plans.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE if exists "stripe"."plans" DROP COLUMN name; -ALTER TABLE if exists "stripe"."plans" DROP COLUMN updated; -ALTER TABLE if exists "stripe"."plans" DROP COLUMN tiers; -ALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_descriptor; -ALTER TABLE if exists "stripe"."plans" DROP COLUMN statement_description; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0023_invoice_deleted.sql b/packages/sync-engine/src/database/migrations/0023_invoice_deleted.sql deleted file mode 100644 index 238f1b53..00000000 --- a/packages/sync-engine/src/database/migrations/0023_invoice_deleted.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TYPE "stripe"."invoice_status" ADD VALUE 'deleted'; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0024_subscription_schedules.sql b/packages/sync-engine/src/database/migrations/0024_subscription_schedules.sql deleted file mode 100644 index a095509b..00000000 --- a/packages/sync-engine/src/database/migrations/0024_subscription_schedules.sql +++ /dev/null @@ -1,29 +0,0 @@ -do $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'subscription_schedule_status') THEN - create type "stripe"."subscription_schedule_status" as enum ('not_started', 'active', 'completed', 'released', 'canceled'); - END IF; -END -$$; - -create table if not exists - "stripe"."subscription_schedules" ( - id text primary key, - object text, - application text, - canceled_at integer, - completed_at integer, - created integer not null, - current_phase jsonb, - customer text not null, - default_settings jsonb, - end_behavior text, - livemode boolean not null, - metadata jsonb not null, - phases jsonb not null, - released_at integer, - released_subscription text, - status stripe.subscription_schedule_status not null, - subscription text, - test_clock text - ); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0025_tax_ids.sql b/packages/sync-engine/src/database/migrations/0025_tax_ids.sql deleted file mode 100644 index c0d063a0..00000000 --- a/packages/sync-engine/src/database/migrations/0025_tax_ids.sql +++ /dev/null @@ -1,14 +0,0 @@ -create table if not exists - "stripe"."tax_ids" ( - "id" text primary key, - "object" text, - "country" text, - "customer" text, - "type" text, - "value" text, - "created" integer not null, - "livemode" boolean, - "owner" jsonb - ); - -create index stripe_tax_ids_customer_idx on "stripe"."tax_ids" using btree (customer); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0026_credit_notes.sql b/packages/sync-engine/src/database/migrations/0026_credit_notes.sql deleted file mode 100644 index 11b04bf8..00000000 --- a/packages/sync-engine/src/database/migrations/0026_credit_notes.sql +++ /dev/null @@ -1,36 +0,0 @@ -create table if not exists - "stripe"."credit_notes" ( - "id" text primary key, - object text, - amount integer, - amount_shipping integer, - created integer, - currency text, - customer text, - customer_balance_transaction text, - discount_amount integer, - discount_amounts jsonb, - invoice text, - lines jsonb, - livemode boolean, - memo text, - metadata jsonb, - number text, - out_of_band_amount integer, - pdf text, - reason text, - refund text, - shipping_cost jsonb, - status text, - subtotal integer, - subtotal_excluding_tax integer, - tax_amounts jsonb, - total integer, - total_excluding_tax integer, - type text, - voided_at text - ); - -create index stripe_credit_notes_customer_idx on "stripe"."credit_notes" using btree (customer); - -create index stripe_credit_notes_invoice_idx on "stripe"."credit_notes" using btree (invoice); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0027_add_marketing_features_to_products.sql b/packages/sync-engine/src/database/migrations/0027_add_marketing_features_to_products.sql deleted file mode 100644 index 3cff7d72..00000000 --- a/packages/sync-engine/src/database/migrations/0027_add_marketing_features_to_products.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS stripe.products ADD COLUMN IF NOT EXISTS marketing_features JSONB; - diff --git a/packages/sync-engine/src/database/migrations/0028_early_fraud_warning.sql b/packages/sync-engine/src/database/migrations/0028_early_fraud_warning.sql deleted file mode 100644 index 603b595a..00000000 --- a/packages/sync-engine/src/database/migrations/0028_early_fraud_warning.sql +++ /dev/null @@ -1,22 +0,0 @@ -create table - if not exists "stripe"."early_fraud_warnings" ( - "id" text primary key, - object text, - actionable boolean, - charge text, - created integer, - fraud_type text, - livemode boolean, - payment_intent text, - updated_at timestamptz default timezone('utc'::text, now()) not null - ); - -create index stripe_early_fraud_warnings_charge_idx on "stripe"."early_fraud_warnings" using btree (charge); - -create index stripe_early_fraud_warnings_payment_intent_idx on "stripe"."early_fraud_warnings" using btree (payment_intent); - -create trigger handle_updated_at - before update - on stripe.early_fraud_warnings - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0029_reviews.sql b/packages/sync-engine/src/database/migrations/0029_reviews.sql deleted file mode 100644 index af254bec..00000000 --- a/packages/sync-engine/src/database/migrations/0029_reviews.sql +++ /dev/null @@ -1,28 +0,0 @@ -create table - if not exists "stripe"."reviews" ( - "id" text primary key, - object text, - billing_zip text, - charge text, - created integer, - closed_reason text, - livemode boolean, - ip_address text, - ip_address_location jsonb, - open boolean, - opened_reason text, - payment_intent text, - reason text, - session text, - updated_at timestamptz default timezone('utc'::text, now()) not null - ); - -create index stripe_reviews_charge_idx on "stripe"."reviews" using btree (charge); - -create index stripe_reviews_payment_intent_idx on "stripe"."reviews" using btree (payment_intent); - -create trigger handle_updated_at - before update - on stripe.reviews - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0030_refunds.sql b/packages/sync-engine/src/database/migrations/0030_refunds.sql deleted file mode 100644 index aa243495..00000000 --- a/packages/sync-engine/src/database/migrations/0030_refunds.sql +++ /dev/null @@ -1,29 +0,0 @@ -create table - if not exists "stripe"."refunds" ( - "id" text primary key, - object text, - amount integer, - balance_transaction text, - charge text, - created integer, - currency text, - destination_details jsonb, - metadata jsonb, - payment_intent text, - reason text, - receipt_number text, - source_transfer_reversal text, - status text, - transfer_reversal text, - updated_at timestamptz default timezone('utc'::text, now()) not null - ); - -create index stripe_refunds_charge_idx on "stripe"."refunds" using btree (charge); - -create index stripe_refunds_payment_intent_idx on "stripe"."refunds" using btree (payment_intent); - -create trigger handle_updated_at - before update - on stripe.refunds - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0031_add_default_price.sql b/packages/sync-engine/src/database/migrations/0031_add_default_price.sql deleted file mode 100644 index 26f662c4..00000000 --- a/packages/sync-engine/src/database/migrations/0031_add_default_price.sql +++ /dev/null @@ -1,2 +0,0 @@ -alter table "stripe"."products" -add column IF NOT EXISTS "default_price" text; diff --git a/packages/sync-engine/src/database/migrations/0032_update_subscription_items.sql b/packages/sync-engine/src/database/migrations/0032_update_subscription_items.sql deleted file mode 100644 index b362e463..00000000 --- a/packages/sync-engine/src/database/migrations/0032_update_subscription_items.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE "stripe"."subscription_items" -ADD COLUMN IF NOT EXISTS "current_period_end" integer, -ADD COLUMN IF NOT EXISTS "current_period_start" integer; diff --git a/packages/sync-engine/src/database/migrations/0033_add_last_synced_at.sql b/packages/sync-engine/src/database/migrations/0033_add_last_synced_at.sql deleted file mode 100644 index 7f552fe7..00000000 --- a/packages/sync-engine/src/database/migrations/0033_add_last_synced_at.sql +++ /dev/null @@ -1,85 +0,0 @@ --- Add last_synced_at column to all Stripe tables for tracking sync status - --- Charges -alter table "stripe"."charges" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Coupons -alter table "stripe"."coupons" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Credit Notes -alter table "stripe"."credit_notes" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Customers -alter table "stripe"."customers" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Disputes -alter table "stripe"."disputes" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Early Fraud Warnings -alter table "stripe"."early_fraud_warnings" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Events -alter table "stripe"."events" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Invoices -alter table "stripe"."invoices" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Payment Intents -alter table "stripe"."payment_intents" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Payment Methods -alter table "stripe"."payment_methods" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Payouts -alter table "stripe"."payouts" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Plans -alter table "stripe"."plans" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Prices -alter table "stripe"."prices" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Products -alter table "stripe"."products" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Refunds -alter table "stripe"."refunds" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Reviews -alter table "stripe"."reviews" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Setup Intents -alter table "stripe"."setup_intents" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Subscription Items -alter table "stripe"."subscription_items" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Subscription Schedules -alter table "stripe"."subscription_schedules" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Subscriptions -alter table "stripe"."subscriptions" -add column IF NOT EXISTS "last_synced_at" timestamptz; - --- Tax IDs -alter table "stripe"."tax_ids" -add column IF NOT EXISTS "last_synced_at" timestamptz; diff --git a/packages/sync-engine/src/database/migrations/0034_remove_foreign_keys.sql b/packages/sync-engine/src/database/migrations/0034_remove_foreign_keys.sql deleted file mode 100644 index b4cd73a4..00000000 --- a/packages/sync-engine/src/database/migrations/0034_remove_foreign_keys.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Remove all foreign key constraints - -ALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS "subscriptions_customer_fkey"; - -ALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS "prices_product_fkey"; - -ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_customer_fkey"; - -ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS "invoices_subscription_fkey"; - -ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_price_fkey"; - -ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS "subscription_items_subscription_fkey"; diff --git a/packages/sync-engine/src/database/migrations/0035_checkout_sessions.sql b/packages/sync-engine/src/database/migrations/0035_checkout_sessions.sql deleted file mode 100644 index 7fe2fb23..00000000 --- a/packages/sync-engine/src/database/migrations/0035_checkout_sessions.sql +++ /dev/null @@ -1,77 +0,0 @@ -create table - if not exists "stripe"."checkout_sessions" ( - "id" text primary key, - "object" text, - "adaptive_pricing" jsonb, - "after_expiration" jsonb, - "allow_promotion_codes" boolean, - "amount_subtotal" integer, - "amount_total" integer, - "automatic_tax" jsonb, - "billing_address_collection" text, - "cancel_url" text, - "client_reference_id" text, - "client_secret" text, - "collected_information" jsonb, - "consent" jsonb, - "consent_collection" jsonb, - "created" integer, - "currency" text, - "currency_conversion" jsonb, - "custom_fields" jsonb, - "custom_text" jsonb, - "customer" text, - "customer_creation" text, - "customer_details" jsonb, - "customer_email" text, - "discounts" jsonb, - "expires_at" integer, - "invoice" text, - "invoice_creation" jsonb, - "livemode" boolean, - "locale" text, - "metadata" jsonb, - "mode" text, - "optional_items" jsonb, - "payment_intent" text, - "payment_link" text, - "payment_method_collection" text, - "payment_method_configuration_details" jsonb, - "payment_method_options" jsonb, - "payment_method_types" jsonb, - "payment_status" text, - "permissions" jsonb, - "phone_number_collection" jsonb, - "presentment_details" jsonb, - "recovered_from" text, - "redirect_on_completion" text, - "return_url" text, - "saved_payment_method_options" jsonb, - "setup_intent" text, - "shipping_address_collection" jsonb, - "shipping_cost" jsonb, - "shipping_details" jsonb, - "shipping_options" jsonb, - "status" text, - "submit_type" text, - "subscription" text, - "success_url" text, - "tax_id_collection" jsonb, - "total_details" jsonb, - "ui_mode" text, - "url" text, - "wallet_options" jsonb, - "updated_at" timestamptz default timezone('utc'::text, now()) not null, - "last_synced_at" timestamptz - ); - -create index stripe_checkout_sessions_customer_idx on "stripe"."checkout_sessions" using btree (customer); -create index stripe_checkout_sessions_subscription_idx on "stripe"."checkout_sessions" using btree (subscription); -create index stripe_checkout_sessions_payment_intent_idx on "stripe"."checkout_sessions" using btree (payment_intent); -create index stripe_checkout_sessions_invoice_idx on "stripe"."checkout_sessions" using btree (invoice); - -create trigger handle_updated_at - before update - on stripe.checkout_sessions - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0036_checkout_session_line_items.sql b/packages/sync-engine/src/database/migrations/0036_checkout_session_line_items.sql deleted file mode 100644 index 848eb062..00000000 --- a/packages/sync-engine/src/database/migrations/0036_checkout_session_line_items.sql +++ /dev/null @@ -1,24 +0,0 @@ -create table if not exists "stripe"."checkout_session_line_items" ( - "id" text primary key, - "object" text, - "amount_discount" integer, - "amount_subtotal" integer, - "amount_tax" integer, - "amount_total" integer, - "currency" text, - "description" text, - "price" text references "stripe"."prices" on delete cascade, - "quantity" integer, - "checkout_session" text references "stripe"."checkout_sessions" on delete cascade, - "updated_at" timestamptz default timezone('utc'::text, now()) not null, - "last_synced_at" timestamptz -); - -create index stripe_checkout_session_line_items_session_idx on "stripe"."checkout_session_line_items" using btree (checkout_session); -create index stripe_checkout_session_line_items_price_idx on "stripe"."checkout_session_line_items" using btree (price); - -create trigger handle_updated_at - before update - on stripe.checkout_session_line_items - for each row - execute procedure set_updated_at(); \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0037_add_features.sql b/packages/sync-engine/src/database/migrations/0037_add_features.sql deleted file mode 100644 index 73e81b0b..00000000 --- a/packages/sync-engine/src/database/migrations/0037_add_features.sql +++ /dev/null @@ -1,18 +0,0 @@ -create table - if not exists "stripe"."features" ( - "id" text primary key, - object text, - livemode boolean, - name text, - lookup_key text unique, - active boolean, - metadata jsonb, - updated_at timestamptz default timezone('utc'::text, now()) not null, - last_synced_at timestamptz - ); - -create trigger handle_updated_at - before update - on stripe.features - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0038_active_entitlement.sql b/packages/sync-engine/src/database/migrations/0038_active_entitlement.sql deleted file mode 100644 index 2a216533..00000000 --- a/packages/sync-engine/src/database/migrations/0038_active_entitlement.sql +++ /dev/null @@ -1,20 +0,0 @@ -create table - if not exists "stripe"."active_entitlements" ( - "id" text primary key, - "object" text, - "livemode" boolean, - "feature" text, - "customer" text, - "lookup_key" text unique, - "updated_at" timestamptz default timezone('utc'::text, now()) not null, - "last_synced_at" timestamptz - ); - -create index stripe_active_entitlements_customer_idx on "stripe"."active_entitlements" using btree (customer); -create index stripe_active_entitlements_feature_idx on "stripe"."active_entitlements" using btree (feature); - -create trigger handle_updated_at - before update - on stripe.active_entitlements - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0039_add_paused_to_subscription_status.sql b/packages/sync-engine/src/database/migrations/0039_add_paused_to_subscription_status.sql deleted file mode 100644 index 442f6456..00000000 --- a/packages/sync-engine/src/database/migrations/0039_add_paused_to_subscription_status.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TYPE "stripe"."subscription_status" ADD VALUE 'paused'; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0040_managed_webhooks.sql b/packages/sync-engine/src/database/migrations/0040_managed_webhooks.sql deleted file mode 100644 index d3d9bc4d..00000000 --- a/packages/sync-engine/src/database/migrations/0040_managed_webhooks.sql +++ /dev/null @@ -1,28 +0,0 @@ -create table - if not exists "stripe"."managed_webhooks" ( - "id" text primary key, - "object" text, - "uuid" text unique not null, - "url" text not null, - "enabled_events" jsonb not null, - "description" text, - "enabled" boolean, - "livemode" boolean, - "metadata" jsonb, - "secret" text not null, - "status" text, - "api_version" text, - "created" integer, - "updated_at" timestamptz default timezone('utc'::text, now()) not null, - "last_synced_at" timestamptz - ); - -create index stripe_managed_webhooks_uuid_idx on "stripe"."managed_webhooks" using btree (uuid); -create index stripe_managed_webhooks_status_idx on "stripe"."managed_webhooks" using btree (status); -create index stripe_managed_webhooks_enabled_idx on "stripe"."managed_webhooks" using btree (enabled); - -create trigger handle_updated_at - before update - on stripe.managed_webhooks - for each row - execute procedure set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0041_rename_managed_webhooks.sql b/packages/sync-engine/src/database/migrations/0041_rename_managed_webhooks.sql deleted file mode 100644 index 6bfb224b..00000000 --- a/packages/sync-engine/src/database/migrations/0041_rename_managed_webhooks.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Rename managed_webhooks table to _managed_webhooks -alter table if exists "stripe"."managed_webhooks" rename to "_managed_webhooks"; diff --git a/packages/sync-engine/src/database/migrations/0042_convert_to_jsonb_generated_columns.sql b/packages/sync-engine/src/database/migrations/0042_convert_to_jsonb_generated_columns.sql deleted file mode 100644 index 25156ce9..00000000 --- a/packages/sync-engine/src/database/migrations/0042_convert_to_jsonb_generated_columns.sql +++ /dev/null @@ -1,1821 +0,0 @@ --- Convert all tables to use jsonb raw_data as source of truth with generated columns --- This migration adds raw_data column and converts all existing columns to generated columns - --- ============================================================================ --- ACTIVE_ENTITLEMENTS --- ============================================================================ - --- Add raw_data column -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes (will be recreated on generated columns) -DROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_customer_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_active_entitlements_feature_idx"; - --- Drop unique constraint (will be recreated on generated column) -ALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS "active_entitlements_lookup_key_key"; - --- Drop existing columns and recreate as generated columns -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((raw_data->>'feature')::text) STORED; - -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>'lookup_key')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer); -CREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature); - --- Recreate unique constraint -CREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL; - --- ============================================================================ --- CHARGES --- ============================================================================ - -ALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid"; -ALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>'paid')::boolean) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order"; -ALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((raw_data->>'order')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>'amount')::bigint) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review"; -ALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>'review')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source"; -ALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (raw_data->'source') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute"; -ALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((raw_data->>'dispute')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>'invoice')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome"; -ALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (raw_data->'outcome') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds"; -ALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (raw_data->'refunds') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured"; -ALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((raw_data->>'captured')::boolean) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded"; -ALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((raw_data->>'refunded')::boolean) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->'shipping') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>'application')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination"; -ALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>'destination')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code"; -ALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>'failure_code')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>'on_behalf_of')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details"; -ALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (raw_data->'fraud_details') STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email"; -ALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>'receipt_email')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>'receipt_number')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>'transfer_group')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded"; -ALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((raw_data->>'amount_refunded')::bigint) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee"; -ALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((raw_data->>'application_fee')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message"; -ALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>'failure_message')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer"; -ALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((raw_data->>'source_transfer')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>'balance_transaction')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details"; -ALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_details') STORED; - --- ============================================================================ --- CHECKOUT_SESSION_LINE_ITEMS --- ============================================================================ - -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_session_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_checkout_session_line_items_price_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((raw_data->>'amount_discount')::integer) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>'amount_subtotal')::integer) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((raw_data->>'amount_tax')::integer) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>'amount_total')::integer) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>'price')::text) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>'quantity')::integer) STORED; - -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((raw_data->>'checkout_session')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session); -CREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price); - --- ============================================================================ --- CHECKOUT_SESSIONS --- ============================================================================ - -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_customer_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_subscription_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_payment_intent_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_checkout_sessions_invoice_idx"; - --- Drop and recreate columns as generated (all columns from checkoutSessionSchema) -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (raw_data->'adaptive_pricing') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (raw_data->'after_expiration') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((raw_data->>'allow_promotion_codes')::boolean) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((raw_data->>'amount_subtotal')::integer) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((raw_data->>'amount_total')::integer) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (raw_data->'automatic_tax') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((raw_data->>'billing_address_collection')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((raw_data->>'cancel_url')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((raw_data->>'client_reference_id')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>'client_secret')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (raw_data->'collected_information') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (raw_data->'consent') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (raw_data->'consent_collection') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (raw_data->'currency_conversion') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->'custom_fields') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (raw_data->'custom_text') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((raw_data->>'customer_creation')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (raw_data->'customer_details') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>'customer_email')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->'discounts') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((raw_data->>'expires_at')::integer) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>'invoice')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (raw_data->'invoice_creation') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((raw_data->>'locale')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((raw_data->>'mode')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (raw_data->'optional_items') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((raw_data->>'payment_link')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((raw_data->>'payment_method_collection')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_configuration_details') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_options') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_types') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((raw_data->>'payment_status')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (raw_data->'permissions') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (raw_data->'phone_number_collection') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (raw_data->'presentment_details') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((raw_data->>'recovered_from')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((raw_data->>'redirect_on_completion')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((raw_data->>'return_url')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->'saved_payment_method_options') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((raw_data->>'setup_intent')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (raw_data->'shipping_address_collection') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->'shipping_cost') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (raw_data->'shipping_details') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (raw_data->'shipping_options') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((raw_data->>'submit_type')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>'subscription')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((raw_data->>'success_url')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (raw_data->'tax_id_collection') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (raw_data->'total_details') STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((raw_data->>'ui_mode')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>'url')::text) STORED; - -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (raw_data->'wallet_options') STORED; - --- Recreate indexes -CREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer); -CREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription); -CREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent); -CREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice); - --- ============================================================================ --- COUPONS --- ============================================================================ - -ALTER TABLE "stripe"."coupons" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>'name')::text) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((raw_data->>'valid')::boolean) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((raw_data->>'duration')::text) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((raw_data->>'redeem_by')::integer) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((raw_data->>'amount_off')::bigint) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((raw_data->>'percent_off')::double precision) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((raw_data->>'times_redeemed')::bigint) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((raw_data->>'max_redemptions')::bigint) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((raw_data->>'duration_in_months')::bigint) STORED; - -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((raw_data->>'percent_off_precise')::double precision) STORED; - --- ============================================================================ --- CREDIT_NOTES --- ============================================================================ - -ALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_credit_notes_customer_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_credit_notes_invoice_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>'amount')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((raw_data->>'amount_shipping')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>'customer_balance_transaction')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((raw_data->>'discount_amount')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->'discount_amounts') STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>'invoice')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->'lines') STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((raw_data->>'memo')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>'number')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((raw_data->>'out_of_band_amount')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((raw_data->>'pdf')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>'reason')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((raw_data->>'refund')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (raw_data->'shipping_cost') STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>'subtotal')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>'subtotal_excluding_tax')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->'tax_amounts') STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((raw_data->>'total')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((raw_data->>'total_excluding_tax')::integer) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((raw_data->>'voided_at')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer); -CREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice); - --- ============================================================================ --- CUSTOMERS --- ============================================================================ - -ALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address"; -ALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (raw_data->'address') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email"; -ALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((raw_data->>'email')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>'name')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone"; -ALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((raw_data->>'phone')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->'shipping') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance"; -ALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((raw_data->>'balance')::integer) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>'default_source')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent"; -ALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((raw_data->>'delinquent')::boolean) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->'discount') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix"; -ALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((raw_data->>'invoice_prefix')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings"; -ALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (raw_data->'invoice_settings') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence"; -ALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((raw_data->>'next_invoice_sequence')::integer) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales"; -ALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (raw_data->'preferred_locales') STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt"; -ALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((raw_data->>'tax_exempt')::text) STORED; - -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted"; -ALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>'deleted')::boolean) STORED; - --- ============================================================================ --- DISPUTES --- ============================================================================ - -ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_dispute_created_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>'amount')::bigint) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>'charge')::text) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>'reason')::text) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (raw_data->'evidence') STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (raw_data->'evidence_details') STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (raw_data->'balance_transactions') STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((raw_data->>'is_charge_refundable')::boolean) STORED; - -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created); - --- ============================================================================ --- EARLY_FRAUD_WARNINGS --- ============================================================================ - -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_charge_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_early_fraud_warnings_payment_intent_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((raw_data->>'actionable')::boolean) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>'charge')::text) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((raw_data->>'fraud_type')::text) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge); -CREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent); - --- ============================================================================ --- EVENTS --- ============================================================================ - -ALTER TABLE "stripe"."events" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data"; -ALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (raw_data->'data') STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request"; -ALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((raw_data->>'request')::text) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version"; -ALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((raw_data->>'api_version')::text) STORED; - -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks"; -ALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((raw_data->>'pending_webhooks')::bigint) STORED; - --- ============================================================================ --- FEATURES --- ============================================================================ - -ALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop unique constraint -ALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS "features_lookup_key_key"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>'name')::text) STORED; - -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>'lookup_key')::text) STORED; - -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>'active')::boolean) STORED; - -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - --- Recreate unique constraint -CREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL; - --- ============================================================================ --- INVOICES --- ============================================================================ - -ALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_invoices_customer_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_invoices_subscription_idx"; - --- Drop and recreate columns as generated (enum status converted to text) -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((raw_data->>'auto_advance')::boolean) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>'collection_method')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((raw_data->>'hosted_invoice_url')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (raw_data->'lines') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((raw_data->>'period_end')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((raw_data->>'period_start')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((raw_data->>'total')::bigint) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((raw_data->>'account_country')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((raw_data->>'account_name')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->'account_tax_ids') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((raw_data->>'amount_due')::bigint) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((raw_data->>'amount_paid')::bigint) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((raw_data->>'amount_remaining')::bigint) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((raw_data->>'application_fee_amount')::bigint) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((raw_data->>'attempt_count')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((raw_data->>'attempted')::boolean) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((raw_data->>'billing_reason')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (raw_data->'custom_fields') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (raw_data->'customer_address') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((raw_data->>'customer_email')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((raw_data->>'customer_name')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((raw_data->>'customer_phone')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (raw_data->'customer_shipping') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((raw_data->>'customer_tax_exempt')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (raw_data->'customer_tax_ids') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->'default_tax_rates') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->'discount') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (raw_data->'discounts') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((raw_data->>'due_date')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((raw_data->>'ending_balance')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((raw_data->>'footer')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((raw_data->>'invoice_pdf')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (raw_data->'last_finalization_error') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((raw_data->>'next_payment_attempt')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((raw_data->>'number')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((raw_data->>'paid')::boolean) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (raw_data->'payment_settings') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>'post_payment_credit_notes_amount')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((raw_data->>'pre_payment_credit_notes_amount')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>'receipt_number')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((raw_data->>'starting_balance')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (raw_data->'status_transitions') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((raw_data->>'subtotal')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((raw_data->>'tax')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (raw_data->'total_discount_amounts') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (raw_data->'total_tax_amounts') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->'transfer_data') STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((raw_data->>'webhooks_delivered_at')::integer) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>'subscription')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>'default_payment_method')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>'default_source')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>'on_behalf_of')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>'charge')::text) STORED; - -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - --- Recreate indexes -CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer); -CREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription); - --- ============================================================================ --- PAYMENT_INTENTS --- ============================================================================ - -ALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_payment_intents_customer_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_payment_intents_invoice_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>'amount')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((raw_data->>'amount_capturable')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (raw_data->'amount_details') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((raw_data->>'amount_received')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>'application')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((raw_data->>'application_fee_amount')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((raw_data->>'automatic_payment_methods')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>'canceled_at')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>'cancellation_reason')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((raw_data->>'capture_method')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((raw_data->>'client_secret')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((raw_data->>'confirmation_method')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((raw_data->>'invoice')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((raw_data->>'last_payment_error')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((raw_data->>'next_action')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>'on_behalf_of')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>'payment_method')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_options') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (raw_data->'payment_method_types') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((raw_data->>'processing')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((raw_data->>'receipt_email')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((raw_data->>'review')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((raw_data->>'setup_future_usage')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (raw_data->'shipping') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor_suffix')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->'transfer_data') STORED; - -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>'transfer_group')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer); -CREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice); - --- ============================================================================ --- PAYMENT_METHODS --- ============================================================================ - -ALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_payment_methods_customer_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (raw_data->'billing_details') STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (raw_data->'card') STORED; - --- Recreate indexes -CREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer); - --- ============================================================================ --- PAYOUTS --- ============================================================================ - -ALTER TABLE "stripe"."payouts" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((raw_data->>'date')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>'amount')::bigint) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((raw_data->>'method')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((raw_data->>'automatic')::boolean) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((raw_data->>'recipient')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((raw_data->>'destination')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((raw_data->>'source_type')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((raw_data->>'arrival_date')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (raw_data->'bank_account') STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((raw_data->>'failure_code')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((raw_data->>'transfer_group')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((raw_data->>'amount_reversed')::bigint) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((raw_data->>'failure_message')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((raw_data->>'source_transaction')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>'balance_transaction')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>'statement_description')::text) STORED; - -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((raw_data->>'failure_balance_transaction')::text) STORED; - --- ============================================================================ --- PLANS --- ============================================================================ - -ALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>'name')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers"; -ALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (raw_data->'tiers') STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>'active')::boolean) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((raw_data->>'amount')::bigint) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product"; -ALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>'product')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval"; -ALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((raw_data->>'interval')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname"; -ALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>'nickname')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode"; -ALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>'tiers_mode')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type"; -ALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((raw_data->>'usage_type')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme"; -ALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>'billing_scheme')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count"; -ALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((raw_data->>'interval_count')::bigint) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage"; -ALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((raw_data->>'aggregate_usage')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage"; -ALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((raw_data->>'transform_usage')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days"; -ALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((raw_data->>'trial_period_days')::bigint) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description"; -ALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((raw_data->>'statement_description')::text) STORED; - --- ============================================================================ --- PRICES --- ============================================================================ - -ALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated (enum types converted to text) -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>'active')::boolean) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname"; -ALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((raw_data->>'nickname')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring"; -ALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (raw_data->'recurring') STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount"; -ALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((raw_data->>'unit_amount')::integer) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme"; -ALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((raw_data->>'billing_scheme')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((raw_data->>'lookup_key')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode"; -ALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((raw_data->>'tiers_mode')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity"; -ALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (raw_data->'transform_quantity') STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal"; -ALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((raw_data->>'unit_amount_decimal')::text) STORED; - -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product"; -ALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((raw_data->>'product')::text) STORED; - --- ============================================================================ --- PRODUCTS --- ============================================================================ - -ALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((raw_data->>'active')::boolean) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price"; -ALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((raw_data->>'default_price')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((raw_data->>'name')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images"; -ALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (raw_data->'images') STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features"; -ALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (raw_data->'marketing_features') STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions"; -ALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (raw_data->'package_dimensions') STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable"; -ALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((raw_data->>'shippable')::boolean) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((raw_data->>'statement_descriptor')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label"; -ALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((raw_data->>'unit_label')::text) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((raw_data->>'updated')::integer) STORED; - -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url"; -ALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((raw_data->>'url')::text) STORED; - --- ============================================================================ --- REFUNDS --- ============================================================================ - -ALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_refunds_charge_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_refunds_payment_intent_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((raw_data->>'amount')::integer) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((raw_data->>'balance_transaction')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>'charge')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((raw_data->>'currency')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (raw_data->'destination_details') STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>'reason')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((raw_data->>'receipt_number')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>'source_transfer_reversal')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((raw_data->>'transfer_reversal')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge); -CREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent); - --- ============================================================================ --- REVIEWS --- ============================================================================ - -ALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_reviews_charge_idx"; -DROP INDEX IF EXISTS "stripe"."stripe_reviews_payment_intent_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((raw_data->>'billing_zip')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((raw_data->>'charge')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((raw_data->>'closed_reason')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((raw_data->>'ip_address')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (raw_data->'ip_address_location') STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((raw_data->>'open')::boolean) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((raw_data->>'opened_reason')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((raw_data->>'payment_intent')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((raw_data->>'reason')::text) STORED; - -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((raw_data->>'session')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge); -CREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent); - --- ============================================================================ --- SETUP_INTENTS --- ============================================================================ - -ALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_setup_intents_customer_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((raw_data->>'description')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((raw_data->>'payment_method')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((raw_data->>'usage')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((raw_data->>'cancellation_reason')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((raw_data->>'latest_attempt')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((raw_data->>'mandate')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((raw_data->>'single_use_mandate')::text) STORED; - -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((raw_data->>'on_behalf_of')::text) STORED; - --- Recreate indexes -CREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer); - --- ============================================================================ --- SUBSCRIPTION_ITEMS --- ============================================================================ - -ALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->'billing_thresholds') STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((raw_data->>'deleted')::boolean) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((raw_data->>'quantity')::integer) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((raw_data->>'price')::text) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>'subscription')::text) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (raw_data->'tax_rates') STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>'current_period_end')::integer) STORED; - -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>'current_period_start')::integer) STORED; - --- ============================================================================ --- SUBSCRIPTION_SCHEDULES --- ============================================================================ - -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated (enum status converted to text) -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((raw_data->>'application')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>'canceled_at')::integer) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((raw_data->>'completed_at')::integer) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (raw_data->'current_phase') STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (raw_data->'default_settings') STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((raw_data->>'end_behavior')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (raw_data->'phases') STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((raw_data->>'released_at')::integer) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((raw_data->>'released_subscription')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((raw_data->>'subscription')::text) STORED; - -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((raw_data->>'test_clock')::text) STORED; - --- ============================================================================ --- SUBSCRIPTIONS --- ============================================================================ - -ALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop and recreate columns as generated (enum status converted to text) -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((raw_data->>'cancel_at_period_end')::boolean) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((raw_data->>'current_period_end')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((raw_data->>'current_period_start')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((raw_data->>'default_payment_method')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (raw_data->'items') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (raw_data->'metadata') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((raw_data->>'pending_setup_intent')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (raw_data->'pending_update') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((raw_data->>'status')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((raw_data->>'application_fee_percent')::double precision) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((raw_data->>'billing_cycle_anchor')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (raw_data->'billing_thresholds') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((raw_data->>'cancel_at')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((raw_data->>'canceled_at')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((raw_data->>'collection_method')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((raw_data->>'days_until_due')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((raw_data->>'default_source')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (raw_data->'default_tax_rates') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (raw_data->'discount') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((raw_data->>'ended_at')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((raw_data->>'next_pending_invoice_item_invoice')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (raw_data->'pause_collection') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (raw_data->'pending_invoice_item_interval') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((raw_data->>'start_date')::integer) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (raw_data->'transfer_data') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (raw_data->'trial_end') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (raw_data->'trial_start') STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((raw_data->>'schedule')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((raw_data->>'latest_invoice')::text) STORED; - -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((raw_data->>'plan')::text) STORED; - --- ============================================================================ --- TAX_IDS --- ============================================================================ - -ALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "raw_data" jsonb; - --- Drop indexes -DROP INDEX IF EXISTS "stripe"."stripe_tax_ids_customer_idx"; - --- Drop and recreate columns as generated -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((raw_data->>'object')::text) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((raw_data->>'country')::text) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((raw_data->>'customer')::text) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((raw_data->>'value')::text) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((raw_data->>'livemode')::boolean) STORED; - -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (raw_data->'owner') STORED; - --- Recreate indexes -CREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer); - diff --git a/packages/sync-engine/src/database/migrations/0043_add_account_id.sql b/packages/sync-engine/src/database/migrations/0043_add_account_id.sql deleted file mode 100644 index c0bf8325..00000000 --- a/packages/sync-engine/src/database/migrations/0043_add_account_id.sql +++ /dev/null @@ -1,49 +0,0 @@ --- Add _account_id column to all tables to track which Stripe account each record belongs to --- Column is nullable for backward compatibility with existing data - -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."charges" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."credit_notes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."customers" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."disputes" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."features" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."invoices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."_managed_webhooks" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."payment_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."payment_methods" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."plans" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."prices" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."products" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."refunds" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."reviews" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."setup_intents" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."subscription_items" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."subscriptions" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - -ALTER TABLE "stripe"."tax_ids" ADD COLUMN IF NOT EXISTS "_account_id" TEXT; - diff --git a/packages/sync-engine/src/database/migrations/0044_make_account_id_required.sql b/packages/sync-engine/src/database/migrations/0044_make_account_id_required.sql deleted file mode 100644 index 05f93ed1..00000000 --- a/packages/sync-engine/src/database/migrations/0044_make_account_id_required.sql +++ /dev/null @@ -1,54 +0,0 @@ --- Make _account_id required by: --- 1. Deleting all rows where _account_id IS NULL --- 2. Setting _account_id to NOT NULL - --- Delete rows with null _account_id -DELETE FROM "stripe"."active_entitlements" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."charges" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."checkout_session_line_items" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."checkout_sessions" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."credit_notes" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."customers" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."disputes" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."early_fraud_warnings" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."features" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."invoices" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."_managed_webhooks" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."payment_intents" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."payment_methods" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."plans" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."prices" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."products" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."refunds" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."reviews" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."setup_intents" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."subscription_items" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."subscription_schedules" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."subscriptions" WHERE "_account_id" IS NULL; -DELETE FROM "stripe"."tax_ids" WHERE "_account_id" IS NULL; - --- Make _account_id NOT NULL -ALTER TABLE "stripe"."active_entitlements" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."charges" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."checkout_session_line_items" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."checkout_sessions" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."credit_notes" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."customers" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."disputes" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."early_fraud_warnings" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."features" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."invoices" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."_managed_webhooks" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."payment_intents" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."payment_methods" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."plans" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."prices" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."products" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."refunds" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."reviews" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."setup_intents" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."subscription_items" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."subscription_schedules" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."subscriptions" ALTER COLUMN "_account_id" SET NOT NULL; -ALTER TABLE "stripe"."tax_ids" ALTER COLUMN "_account_id" SET NOT NULL; - diff --git a/packages/sync-engine/src/database/migrations/0045_sync_status.sql b/packages/sync-engine/src/database/migrations/0045_sync_status.sql deleted file mode 100644 index a9038d10..00000000 --- a/packages/sync-engine/src/database/migrations/0045_sync_status.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Create _sync_status metadata table for tracking incremental sync cursors --- This table tracks the state and progress of each resource's synchronization - -CREATE TABLE IF NOT EXISTS "stripe"."_sync_status" ( - id serial PRIMARY KEY, - resource text UNIQUE NOT NULL, - status text CHECK (status IN ('idle', 'running', 'complete', 'error')) DEFAULT 'idle', - last_synced_at timestamptz DEFAULT now(), - last_incremental_cursor timestamptz, - error_message text, - updated_at timestamptz DEFAULT now() -); - --- Use existing set_updated_at() function created in migration 0012 -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_status" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at(); diff --git a/packages/sync-engine/src/database/migrations/0046_sync_status_per_account.sql b/packages/sync-engine/src/database/migrations/0046_sync_status_per_account.sql deleted file mode 100644 index 6e948933..00000000 --- a/packages/sync-engine/src/database/migrations/0046_sync_status_per_account.sql +++ /dev/null @@ -1,91 +0,0 @@ --- Add _account_id to _sync_status table to track sync cursors per account --- This enables proper cursor isolation when syncing multiple Stripe accounts --- --- Breaking change: All existing cursor data will be deleted (clean slate) --- Next sync will perform a full backfill for each account - --- Step 1: Delete all existing cursor data -DELETE FROM "stripe"."_sync_status"; - --- Step 2: Add _account_id column -ALTER TABLE "stripe"."_sync_status" ADD COLUMN "_account_id" TEXT NOT NULL; - --- Step 3: Drop existing unique constraint on resource -ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_key; - --- Step 4: Add new composite unique constraint on (resource, _account_id) -ALTER TABLE "stripe"."_sync_status" - ADD CONSTRAINT _sync_status_resource_account_key - UNIQUE (resource, "_account_id"); - --- Step 5: Add index for efficient lookups -CREATE INDEX IF NOT EXISTS idx_sync_status_resource_account - ON "stripe"."_sync_status" (resource, "_account_id"); - --- Step 6: Create accounts table to track Stripe accounts (JSONB with generated columns) -CREATE TABLE IF NOT EXISTS "stripe"."accounts" ( - id TEXT PRIMARY KEY, - raw_data JSONB NOT NULL, - first_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(), - last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - -- Generated columns extracted from raw_data - business_name TEXT GENERATED ALWAYS AS ((raw_data->'business_profile'->>'name')::text) STORED, - email TEXT GENERATED ALWAYS AS ((raw_data->>'email')::text) STORED, - type TEXT GENERATED ALWAYS AS ((raw_data->>'type')::text) STORED, - charges_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>'charges_enabled')::boolean) STORED, - payouts_enabled BOOLEAN GENERATED ALWAYS AS ((raw_data->>'payouts_enabled')::boolean) STORED, - details_submitted BOOLEAN GENERATED ALWAYS AS ((raw_data->>'details_submitted')::boolean) STORED, - country TEXT GENERATED ALWAYS AS ((raw_data->>'country')::text) STORED, - default_currency TEXT GENERATED ALWAYS AS ((raw_data->>'default_currency')::text) STORED, - created INTEGER GENERATED ALWAYS AS ((raw_data->>'created')::integer) STORED -); - --- Step 7: Add index for account name lookups -CREATE INDEX IF NOT EXISTS idx_accounts_business_name - ON "stripe"."accounts" (business_name); - --- Step 8: Add updated_at trigger for accounts -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."accounts" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at(); - --- Step 9: Backfill accounts from existing data tables -INSERT INTO "stripe"."accounts" (id, raw_data, first_synced_at, last_synced_at) -SELECT DISTINCT - "_account_id" as id, - jsonb_build_object('id', "_account_id", 'type', 'unknown') as raw_data, - now() as first_synced_at, - now() as last_synced_at -FROM "stripe"."products" -WHERE "_account_id" IS NOT NULL -ON CONFLICT (id) DO NOTHING; - --- Step 10: Add foreign key constraints from data tables to accounts -ALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); - --- Step 11: Add foreign key from _sync_status to accounts -ALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); diff --git a/packages/sync-engine/src/database/migrations/0047_api_key_hashes.sql b/packages/sync-engine/src/database/migrations/0047_api_key_hashes.sql deleted file mode 100644 index 3c2ecd63..00000000 --- a/packages/sync-engine/src/database/migrations/0047_api_key_hashes.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Add api_key_hashes array column to accounts table --- This stores SHA-256 hashes of Stripe API keys for fast account lookups --- Enables lookup of account ID by API key hash without making Stripe API calls --- Supports multiple API keys per account (test/live keys, rotated keys, etc.) - --- Step 1: Add api_key_hashes column as TEXT array -ALTER TABLE "stripe"."accounts" ADD COLUMN "api_key_hashes" TEXT[] DEFAULT '{}'; - --- Step 2: Create GIN index for fast array containment lookups --- This enables efficient queries like: WHERE 'hash_value' = ANY(api_key_hashes) -CREATE INDEX IF NOT EXISTS idx_accounts_api_key_hashes - ON "stripe"."accounts" USING GIN (api_key_hashes); diff --git a/packages/sync-engine/src/database/migrations/0048_rename_reserved_columns.sql b/packages/sync-engine/src/database/migrations/0048_rename_reserved_columns.sql deleted file mode 100644 index e3db9426..00000000 --- a/packages/sync-engine/src/database/migrations/0048_rename_reserved_columns.sql +++ /dev/null @@ -1,1253 +0,0 @@ --- Rename all reserved column names to use underscore prefix --- This clearly distinguishes system-managed columns from user-accessible data --- --- Changes: --- - id -> _id (primary key for all tables) --- - raw_data -> _raw_data (JSONB source of truth) --- - last_synced_at -> _last_synced_at (sync timestamp) --- - updated_at -> _updated_at (last update timestamp) --- - _account_id remains unchanged (already has underscore prefix) - --- ============================================================================ --- STEP 1: RENAME BASE COLUMNS FOR ALL TABLES --- ============================================================================ - --- This renames id, raw_data, last_synced_at, and updated_at for all tables --- PostgreSQL automatically updates primary keys, foreign keys, and indexes - --- active_entitlements -ALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."active_entitlements" RENAME COLUMN "updated_at" TO "_updated_at"; - --- charges -ALTER TABLE "stripe"."charges" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."charges" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."charges" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."charges" RENAME COLUMN "updated_at" TO "_updated_at"; - --- checkout_session_line_items -ALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."checkout_session_line_items" RENAME COLUMN "updated_at" TO "_updated_at"; - --- checkout_sessions -ALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."checkout_sessions" RENAME COLUMN "updated_at" TO "_updated_at"; - --- credit_notes -ALTER TABLE "stripe"."credit_notes" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."credit_notes" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."credit_notes" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- coupons -ALTER TABLE "stripe"."coupons" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."coupons" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."coupons" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."coupons" RENAME COLUMN "updated_at" TO "_updated_at"; - --- customers -ALTER TABLE "stripe"."customers" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."customers" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."customers" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."customers" RENAME COLUMN "updated_at" TO "_updated_at"; - --- disputes -ALTER TABLE "stripe"."disputes" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."disputes" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."disputes" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."disputes" RENAME COLUMN "updated_at" TO "_updated_at"; - --- early_fraud_warnings -ALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."early_fraud_warnings" RENAME COLUMN "updated_at" TO "_updated_at"; - --- events -ALTER TABLE "stripe"."events" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."events" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."events" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."events" RENAME COLUMN "updated_at" TO "_updated_at"; - --- features -ALTER TABLE "stripe"."features" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."features" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."features" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."features" RENAME COLUMN "updated_at" TO "_updated_at"; - --- invoices -ALTER TABLE "stripe"."invoices" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."invoices" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."invoices" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."invoices" RENAME COLUMN "updated_at" TO "_updated_at"; - --- _managed_webhooks -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "updated_at" TO "_updated_at"; - --- payment_intents -ALTER TABLE "stripe"."payment_intents" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."payment_intents" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."payment_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- payment_methods -ALTER TABLE "stripe"."payment_methods" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."payment_methods" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."payment_methods" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- payouts -ALTER TABLE "stripe"."payouts" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."payouts" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."payouts" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."payouts" RENAME COLUMN "updated_at" TO "_updated_at"; - --- plans -ALTER TABLE "stripe"."plans" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."plans" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."plans" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."plans" RENAME COLUMN "updated_at" TO "_updated_at"; - --- prices -ALTER TABLE "stripe"."prices" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."prices" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."prices" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."prices" RENAME COLUMN "updated_at" TO "_updated_at"; - --- products -ALTER TABLE "stripe"."products" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."products" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."products" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."products" RENAME COLUMN "updated_at" TO "_updated_at"; - --- refunds -ALTER TABLE "stripe"."refunds" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."refunds" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."refunds" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."refunds" RENAME COLUMN "updated_at" TO "_updated_at"; - --- reviews -ALTER TABLE "stripe"."reviews" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."reviews" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."reviews" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."reviews" RENAME COLUMN "updated_at" TO "_updated_at"; - --- setup_intents -ALTER TABLE "stripe"."setup_intents" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."setup_intents" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."setup_intents" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- subscription_items -ALTER TABLE "stripe"."subscription_items" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."subscription_items" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."subscription_items" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- subscription_schedules -ALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."subscription_schedules" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- subscriptions -ALTER TABLE "stripe"."subscriptions" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."subscriptions" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."subscriptions" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."subscriptions" RENAME COLUMN "updated_at" TO "_updated_at"; - --- tax_ids -ALTER TABLE "stripe"."tax_ids" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."tax_ids" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."tax_ids" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; - --- Metadata Tables - --- _sync_status -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "updated_at" TO "_updated_at"; - --- accounts -ALTER TABLE "stripe"."accounts" RENAME COLUMN "id" TO "_id"; -ALTER TABLE "stripe"."accounts" RENAME COLUMN "raw_data" TO "_raw_data"; -ALTER TABLE "stripe"."accounts" RENAME COLUMN "last_synced_at" TO "_last_synced_at"; -ALTER TABLE "stripe"."accounts" RENAME COLUMN "updated_at" TO "_updated_at"; - --- ============================================================================ --- STEP 1.5: UPDATE TRIGGER FUNCTION TO USE NEW COLUMN NAME --- ============================================================================ - --- Now that all columns are renamed, update the trigger function to reference _updated_at -CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger - LANGUAGE plpgsql -AS $$ -begin - new._updated_at = now(); - return NEW; -end; -$$; - --- ============================================================================ --- STEP 2: RECREATE GENERATED COLUMNS TO REFERENCE _raw_data --- ============================================================================ - --- All generated columns must be dropped and recreated to reference the new --- _raw_data column name instead of raw_data - -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "feature"; -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "paid"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "order"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "review"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "dispute"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "outcome"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunds"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "captured"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "refunded"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "destination"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_code"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "fraud_details"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_email"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "amount_refunded"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "application_fee"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "failure_message"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "source_transfer"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "payment_method_details"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_discount"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_subtotal"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_tax"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "amount_total"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "price"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "quantity"; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "checkout_session"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "adaptive_pricing"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "after_expiration"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "allow_promotion_codes"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_subtotal"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "amount_total"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "automatic_tax"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "billing_address_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "cancel_url"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_reference_id"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "client_secret"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "collected_information"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "consent_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "currency_conversion"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_fields"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "custom_text"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_creation"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_details"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "customer_email"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "discounts"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "expires_at"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "invoice_creation"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "locale"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "mode"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "optional_items"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_link"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_configuration_details"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_options"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_method_types"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "payment_status"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "permissions"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "phone_number_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "presentment_details"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "recovered_from"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "redirect_on_completion"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "return_url"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "saved_payment_method_options"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "setup_intent"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_address_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_cost"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_details"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "shipping_options"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "submit_type"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "success_url"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "tax_id_collection"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "total_details"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "ui_mode"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "url"; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "wallet_options"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "valid"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "redeem_by"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "amount_off"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "times_redeemed"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "max_redemptions"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "duration_in_months"; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "percent_off_precise"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "amount_shipping"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "customer_balance_transaction"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amount"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "discount_amounts"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "lines"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "memo"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "number"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "out_of_band_amount"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "pdf"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "refund"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "shipping_cost"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "subtotal_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "tax_amounts"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "total_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "voided_at"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "address"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "email"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "phone"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "balance"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "delinquent"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_prefix"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "invoice_settings"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "next_invoice_sequence"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "preferred_locales"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "tax_exempt"; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "deleted"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "evidence_details"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "balance_transactions"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "is_charge_refundable"; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "actionable"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "fraud_type"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "data"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "request"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "api_version"; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "pending_webhooks"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "auto_advance"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "collection_method"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "hosted_invoice_url"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "lines"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_end"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "period_start"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_country"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_name"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "account_tax_ids"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_due"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_paid"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "amount_remaining"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "application_fee_amount"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempt_count"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "attempted"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "billing_reason"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "custom_fields"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_address"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_email"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_name"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_phone"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_shipping"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_exempt"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer_tax_ids"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_tax_rates"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "discounts"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "due_date"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "ending_balance"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "footer"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "invoice_pdf"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "last_finalization_error"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "next_payment_attempt"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "number"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "paid"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_settings"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "post_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "pre_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "starting_balance"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "status_transitions"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subtotal"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "tax"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_discount_amounts"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "total_tax_amounts"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "webhooks_delivered_at"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_payment_method"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_capturable"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_details"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "amount_received"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "application_fee_amount"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "automatic_payment_methods"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "cancellation_reason"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "capture_method"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "client_secret"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "confirmation_method"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "invoice"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "last_payment_error"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "next_action"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_options"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "payment_method_types"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "processing"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "receipt_email"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "review"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "setup_future_usage"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "shipping"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "statement_descriptor_suffix"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "billing_details"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "card"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "date"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "method"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "automatic"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "recipient"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "destination"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_type"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "arrival_date"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "bank_account"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_code"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "transfer_group"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "amount_reversed"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_message"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "source_transaction"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "statement_description"; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "failure_balance_transaction"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "product"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "nickname"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "tiers_mode"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "usage_type"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "billing_scheme"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "interval_count"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "aggregate_usage"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "transform_usage"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "trial_period_days"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "statement_description"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "nickname"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "recurring"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "billing_scheme"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "lookup_key"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "tiers_mode"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "transform_quantity"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "unit_amount_decimal"; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "product"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "active"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "default_price"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "name"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "images"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "marketing_features"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "package_dimensions"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "shippable"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "statement_descriptor"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "unit_label"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "updated"; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "url"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "amount"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "balance_transaction"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "currency"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "destination_details"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "receipt_number"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "source_transfer_reversal"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "transfer_reversal"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "billing_zip"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "charge"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "closed_reason"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "ip_address_location"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "open"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "opened_reason"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "payment_intent"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "reason"; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "session"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "description"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "payment_method"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "usage"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "cancellation_reason"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "latest_attempt"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "mandate"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "single_use_mandate"; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "on_behalf_of"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "billing_thresholds"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "deleted"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "quantity"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "price"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "tax_rates"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_end"; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "current_period_start"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "application"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "completed_at"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "current_phase"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "default_settings"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "end_behavior"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "phases"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_at"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "released_subscription"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "subscription"; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "test_clock"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at_period_end"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_end"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "current_period_start"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_payment_method"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "items"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "metadata"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_setup_intent"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_update"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "status"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "application_fee_percent"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_cycle_anchor"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "billing_thresholds"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "cancel_at"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "canceled_at"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "collection_method"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "days_until_due"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_source"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "default_tax_rates"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "discount"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "ended_at"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "next_pending_invoice_item_invoice"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pause_collection"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "pending_invoice_item_interval"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "start_date"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "transfer_data"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_end"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "trial_start"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "schedule"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "latest_invoice"; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "plan"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "object"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "country"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "customer"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "type"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "value"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "created"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "livemode"; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "owner"; - --- Add generated columns back with _raw_data references - -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "feature" text GENERATED ALWAYS AS ((_raw_data->>'feature')::text) STORED; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>'lookup_key')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>'paid')::boolean) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "order" text GENERATED ALWAYS AS ((_raw_data->>'order')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>'review')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "source" jsonb GENERATED ALWAYS AS (_raw_data->'source') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "dispute" text GENERATED ALWAYS AS ((_raw_data->>'dispute')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>'invoice')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "outcome" jsonb GENERATED ALWAYS AS (_raw_data->'outcome') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "refunds" jsonb GENERATED ALWAYS AS (_raw_data->'refunds') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "captured" boolean GENERATED ALWAYS AS ((_raw_data->>'captured')::boolean) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "refunded" boolean GENERATED ALWAYS AS ((_raw_data->>'refunded')::boolean) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->'shipping') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>'application')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>'destination')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>'failure_code')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>'on_behalf_of')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "fraud_details" jsonb GENERATED ALWAYS AS (_raw_data->'fraud_details') STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>'receipt_email')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>'receipt_number')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>'transfer_group')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "amount_refunded" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_refunded')::bigint) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "application_fee" text GENERATED ALWAYS AS ((_raw_data->>'application_fee')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>'failure_message')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "source_transfer" text GENERATED ALWAYS AS ((_raw_data->>'source_transfer')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>'balance_transaction')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."charges" ADD COLUMN "payment_method_details" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_details') STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" integer GENERATED ALWAYS AS ((_raw_data->>'amount_discount')::integer) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>'amount_subtotal')::integer) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" integer GENERATED ALWAYS AS ((_raw_data->>'amount_tax')::integer) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>'amount_total')::integer) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>'price')::text) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>'quantity')::integer) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "checkout_session" text GENERATED ALWAYS AS ((_raw_data->>'checkout_session')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "adaptive_pricing" jsonb GENERATED ALWAYS AS (_raw_data->'adaptive_pricing') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "after_expiration" jsonb GENERATED ALWAYS AS (_raw_data->'after_expiration') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "allow_promotion_codes" boolean GENERATED ALWAYS AS ((_raw_data->>'allow_promotion_codes')::boolean) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" integer GENERATED ALWAYS AS ((_raw_data->>'amount_subtotal')::integer) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" integer GENERATED ALWAYS AS ((_raw_data->>'amount_total')::integer) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "automatic_tax" jsonb GENERATED ALWAYS AS (_raw_data->'automatic_tax') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "billing_address_collection" text GENERATED ALWAYS AS ((_raw_data->>'billing_address_collection')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "cancel_url" text GENERATED ALWAYS AS ((_raw_data->>'cancel_url')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_reference_id" text GENERATED ALWAYS AS ((_raw_data->>'client_reference_id')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>'client_secret')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "collected_information" jsonb GENERATED ALWAYS AS (_raw_data->'collected_information') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent" jsonb GENERATED ALWAYS AS (_raw_data->'consent') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "consent_collection" jsonb GENERATED ALWAYS AS (_raw_data->'consent_collection') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "currency_conversion" jsonb GENERATED ALWAYS AS (_raw_data->'currency_conversion') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->'custom_fields') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "custom_text" jsonb GENERATED ALWAYS AS (_raw_data->'custom_text') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_creation" text GENERATED ALWAYS AS ((_raw_data->>'customer_creation')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_details" jsonb GENERATED ALWAYS AS (_raw_data->'customer_details') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>'customer_email')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->'discounts') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "expires_at" integer GENERATED ALWAYS AS ((_raw_data->>'expires_at')::integer) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>'invoice')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "invoice_creation" jsonb GENERATED ALWAYS AS (_raw_data->'invoice_creation') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "locale" text GENERATED ALWAYS AS ((_raw_data->>'locale')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "mode" text GENERATED ALWAYS AS ((_raw_data->>'mode')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "optional_items" jsonb GENERATED ALWAYS AS (_raw_data->'optional_items') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_link" text GENERATED ALWAYS AS ((_raw_data->>'payment_link')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_collection" text GENERATED ALWAYS AS ((_raw_data->>'payment_method_collection')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_configuration_details" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_configuration_details') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_options') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_types') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "payment_status" text GENERATED ALWAYS AS ((_raw_data->>'payment_status')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "permissions" jsonb GENERATED ALWAYS AS (_raw_data->'permissions') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "phone_number_collection" jsonb GENERATED ALWAYS AS (_raw_data->'phone_number_collection') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "presentment_details" jsonb GENERATED ALWAYS AS (_raw_data->'presentment_details') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "recovered_from" text GENERATED ALWAYS AS ((_raw_data->>'recovered_from')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "redirect_on_completion" text GENERATED ALWAYS AS ((_raw_data->>'redirect_on_completion')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "return_url" text GENERATED ALWAYS AS ((_raw_data->>'return_url')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "saved_payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->'saved_payment_method_options') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "setup_intent" text GENERATED ALWAYS AS ((_raw_data->>'setup_intent')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_address_collection" jsonb GENERATED ALWAYS AS (_raw_data->'shipping_address_collection') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->'shipping_cost') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_details" jsonb GENERATED ALWAYS AS (_raw_data->'shipping_details') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "shipping_options" jsonb GENERATED ALWAYS AS (_raw_data->'shipping_options') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "submit_type" text GENERATED ALWAYS AS ((_raw_data->>'submit_type')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>'subscription')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "success_url" text GENERATED ALWAYS AS ((_raw_data->>'success_url')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "tax_id_collection" jsonb GENERATED ALWAYS AS (_raw_data->'tax_id_collection') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "total_details" jsonb GENERATED ALWAYS AS (_raw_data->'total_details') STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "ui_mode" text GENERATED ALWAYS AS ((_raw_data->>'ui_mode')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>'url')::text) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "wallet_options" jsonb GENERATED ALWAYS AS (_raw_data->'wallet_options') STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>'name')::text) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "valid" boolean GENERATED ALWAYS AS ((_raw_data->>'valid')::boolean) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "duration" text GENERATED ALWAYS AS ((_raw_data->>'duration')::text) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "redeem_by" integer GENERATED ALWAYS AS ((_raw_data->>'redeem_by')::integer) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "amount_off" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_off')::bigint) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off" double precision GENERATED ALWAYS AS ((_raw_data->>'percent_off')::double precision) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "times_redeemed" bigint GENERATED ALWAYS AS ((_raw_data->>'times_redeemed')::bigint) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "max_redemptions" bigint GENERATED ALWAYS AS ((_raw_data->>'max_redemptions')::bigint) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "duration_in_months" bigint GENERATED ALWAYS AS ((_raw_data->>'duration_in_months')::bigint) STORED; -ALTER TABLE "stripe"."coupons" ADD COLUMN "percent_off_precise" double precision GENERATED ALWAYS AS ((_raw_data->>'percent_off_precise')::double precision) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>'amount')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" integer GENERATED ALWAYS AS ((_raw_data->>'amount_shipping')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "customer_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>'customer_balance_transaction')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" integer GENERATED ALWAYS AS ((_raw_data->>'discount_amount')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->'discount_amounts') STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>'invoice')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->'lines') STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "memo" text GENERATED ALWAYS AS ((_raw_data->>'memo')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>'number')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" integer GENERATED ALWAYS AS ((_raw_data->>'out_of_band_amount')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "pdf" text GENERATED ALWAYS AS ((_raw_data->>'pdf')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>'reason')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "refund" text GENERATED ALWAYS AS ((_raw_data->>'refund')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "shipping_cost" jsonb GENERATED ALWAYS AS (_raw_data->'shipping_cost') STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>'subtotal')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>'subtotal_excluding_tax')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->'tax_amounts') STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" integer GENERATED ALWAYS AS ((_raw_data->>'total')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" integer GENERATED ALWAYS AS ((_raw_data->>'total_excluding_tax')::integer) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "voided_at" text GENERATED ALWAYS AS ((_raw_data->>'voided_at')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "address" jsonb GENERATED ALWAYS AS (_raw_data->'address') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "email" text GENERATED ALWAYS AS ((_raw_data->>'email')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>'name')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "phone" text GENERATED ALWAYS AS ((_raw_data->>'phone')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->'shipping') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "balance" integer GENERATED ALWAYS AS ((_raw_data->>'balance')::integer) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>'default_source')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "delinquent" boolean GENERATED ALWAYS AS ((_raw_data->>'delinquent')::boolean) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->'discount') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "invoice_prefix" text GENERATED ALWAYS AS ((_raw_data->>'invoice_prefix')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "invoice_settings" jsonb GENERATED ALWAYS AS (_raw_data->'invoice_settings') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "next_invoice_sequence" integer GENERATED ALWAYS AS ((_raw_data->>'next_invoice_sequence')::integer) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "preferred_locales" jsonb GENERATED ALWAYS AS (_raw_data->'preferred_locales') STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>'tax_exempt')::text) STORED; -ALTER TABLE "stripe"."customers" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>'deleted')::boolean) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>'charge')::text) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>'reason')::text) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "evidence" jsonb GENERATED ALWAYS AS (_raw_data->'evidence') STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "evidence_details" jsonb GENERATED ALWAYS AS (_raw_data->'evidence_details') STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "balance_transactions" jsonb GENERATED ALWAYS AS (_raw_data->'balance_transactions') STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "is_charge_refundable" boolean GENERATED ALWAYS AS ((_raw_data->>'is_charge_refundable')::boolean) STORED; -ALTER TABLE "stripe"."disputes" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "actionable" boolean GENERATED ALWAYS AS ((_raw_data->>'actionable')::boolean) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>'charge')::text) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "fraud_type" text GENERATED ALWAYS AS ((_raw_data->>'fraud_type')::text) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "data" jsonb GENERATED ALWAYS AS (_raw_data->'data') STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "request" text GENERATED ALWAYS AS ((_raw_data->>'request')::text) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "api_version" text GENERATED ALWAYS AS ((_raw_data->>'api_version')::text) STORED; -ALTER TABLE "stripe"."events" ADD COLUMN "pending_webhooks" bigint GENERATED ALWAYS AS ((_raw_data->>'pending_webhooks')::bigint) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>'name')::text) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>'lookup_key')::text) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>'active')::boolean) STORED; -ALTER TABLE "stripe"."features" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "auto_advance" boolean GENERATED ALWAYS AS ((_raw_data->>'auto_advance')::boolean) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>'collection_method')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "hosted_invoice_url" text GENERATED ALWAYS AS ((_raw_data->>'hosted_invoice_url')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "lines" jsonb GENERATED ALWAYS AS (_raw_data->'lines') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "period_end" integer GENERATED ALWAYS AS ((_raw_data->>'period_end')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "period_start" integer GENERATED ALWAYS AS ((_raw_data->>'period_start')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>'total')::bigint) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_country" text GENERATED ALWAYS AS ((_raw_data->>'account_country')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_name" text GENERATED ALWAYS AS ((_raw_data->>'account_name')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "account_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->'account_tax_ids') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_due" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_due')::bigint) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_paid" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_paid')::bigint) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "amount_remaining" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_remaining')::bigint) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'application_fee_amount')::bigint) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "attempt_count" integer GENERATED ALWAYS AS ((_raw_data->>'attempt_count')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "attempted" boolean GENERATED ALWAYS AS ((_raw_data->>'attempted')::boolean) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "billing_reason" text GENERATED ALWAYS AS ((_raw_data->>'billing_reason')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "custom_fields" jsonb GENERATED ALWAYS AS (_raw_data->'custom_fields') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_address" jsonb GENERATED ALWAYS AS (_raw_data->'customer_address') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_email" text GENERATED ALWAYS AS ((_raw_data->>'customer_email')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_name" text GENERATED ALWAYS AS ((_raw_data->>'customer_name')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_phone" text GENERATED ALWAYS AS ((_raw_data->>'customer_phone')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_shipping" jsonb GENERATED ALWAYS AS (_raw_data->'customer_shipping') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_exempt" text GENERATED ALWAYS AS ((_raw_data->>'customer_tax_exempt')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer_tax_ids" jsonb GENERATED ALWAYS AS (_raw_data->'customer_tax_ids') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->'default_tax_rates') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->'discount') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "discounts" jsonb GENERATED ALWAYS AS (_raw_data->'discounts') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "due_date" integer GENERATED ALWAYS AS ((_raw_data->>'due_date')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" integer GENERATED ALWAYS AS ((_raw_data->>'ending_balance')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "footer" text GENERATED ALWAYS AS ((_raw_data->>'footer')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "invoice_pdf" text GENERATED ALWAYS AS ((_raw_data->>'invoice_pdf')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "last_finalization_error" jsonb GENERATED ALWAYS AS (_raw_data->'last_finalization_error') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "next_payment_attempt" integer GENERATED ALWAYS AS ((_raw_data->>'next_payment_attempt')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "number" text GENERATED ALWAYS AS ((_raw_data->>'number')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "paid" boolean GENERATED ALWAYS AS ((_raw_data->>'paid')::boolean) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "payment_settings" jsonb GENERATED ALWAYS AS (_raw_data->'payment_settings') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>'post_payment_credit_notes_amount')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" integer GENERATED ALWAYS AS ((_raw_data->>'pre_payment_credit_notes_amount')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>'receipt_number')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" integer GENERATED ALWAYS AS ((_raw_data->>'starting_balance')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "status_transitions" jsonb GENERATED ALWAYS AS (_raw_data->'status_transitions') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" integer GENERATED ALWAYS AS ((_raw_data->>'subtotal')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "tax" integer GENERATED ALWAYS AS ((_raw_data->>'tax')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total_discount_amounts" jsonb GENERATED ALWAYS AS (_raw_data->'total_discount_amounts') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "total_tax_amounts" jsonb GENERATED ALWAYS AS (_raw_data->'total_tax_amounts') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->'transfer_data') STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "webhooks_delivered_at" integer GENERATED ALWAYS AS ((_raw_data->>'webhooks_delivered_at')::integer) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>'subscription')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>'default_payment_method')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>'default_source')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>'on_behalf_of')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>'charge')::text) STORED; -ALTER TABLE "stripe"."invoices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>'amount')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" integer GENERATED ALWAYS AS ((_raw_data->>'amount_capturable')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_details" jsonb GENERATED ALWAYS AS (_raw_data->'amount_details') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" integer GENERATED ALWAYS AS ((_raw_data->>'amount_received')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>'application')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" integer GENERATED ALWAYS AS ((_raw_data->>'application_fee_amount')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "automatic_payment_methods" text GENERATED ALWAYS AS ((_raw_data->>'automatic_payment_methods')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>'canceled_at')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>'cancellation_reason')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "capture_method" text GENERATED ALWAYS AS ((_raw_data->>'capture_method')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "client_secret" text GENERATED ALWAYS AS ((_raw_data->>'client_secret')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "confirmation_method" text GENERATED ALWAYS AS ((_raw_data->>'confirmation_method')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "invoice" text GENERATED ALWAYS AS ((_raw_data->>'invoice')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "last_payment_error" text GENERATED ALWAYS AS ((_raw_data->>'last_payment_error')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "next_action" text GENERATED ALWAYS AS ((_raw_data->>'next_action')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>'on_behalf_of')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>'payment_method')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_options" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_options') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "payment_method_types" jsonb GENERATED ALWAYS AS (_raw_data->'payment_method_types') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "processing" text GENERATED ALWAYS AS ((_raw_data->>'processing')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "receipt_email" text GENERATED ALWAYS AS ((_raw_data->>'receipt_email')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "review" text GENERATED ALWAYS AS ((_raw_data->>'review')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "setup_future_usage" text GENERATED ALWAYS AS ((_raw_data->>'setup_future_usage')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "shipping" jsonb GENERATED ALWAYS AS (_raw_data->'shipping') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "statement_descriptor_suffix" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor_suffix')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->'transfer_data') STORED; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>'transfer_group')::text) STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "billing_details" jsonb GENERATED ALWAYS AS (_raw_data->'billing_details') STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "card" jsonb GENERATED ALWAYS AS (_raw_data->'card') STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "date" text GENERATED ALWAYS AS ((_raw_data->>'date')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "method" text GENERATED ALWAYS AS ((_raw_data->>'method')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "automatic" boolean GENERATED ALWAYS AS ((_raw_data->>'automatic')::boolean) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "recipient" text GENERATED ALWAYS AS ((_raw_data->>'recipient')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "destination" text GENERATED ALWAYS AS ((_raw_data->>'destination')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "source_type" text GENERATED ALWAYS AS ((_raw_data->>'source_type')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "arrival_date" text GENERATED ALWAYS AS ((_raw_data->>'arrival_date')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "bank_account" jsonb GENERATED ALWAYS AS (_raw_data->'bank_account') STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_code" text GENERATED ALWAYS AS ((_raw_data->>'failure_code')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "transfer_group" text GENERATED ALWAYS AS ((_raw_data->>'transfer_group')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "amount_reversed" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_reversed')::bigint) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_message" text GENERATED ALWAYS AS ((_raw_data->>'failure_message')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "source_transaction" text GENERATED ALWAYS AS ((_raw_data->>'source_transaction')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>'balance_transaction')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>'statement_description')::text) STORED; -ALTER TABLE "stripe"."payouts" ADD COLUMN "failure_balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>'failure_balance_transaction')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>'name')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "tiers" jsonb GENERATED ALWAYS AS (_raw_data->'tiers') STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>'active')::boolean) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>'product')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "interval" text GENERATED ALWAYS AS ((_raw_data->>'interval')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>'nickname')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>'tiers_mode')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "usage_type" text GENERATED ALWAYS AS ((_raw_data->>'usage_type')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>'billing_scheme')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "interval_count" bigint GENERATED ALWAYS AS ((_raw_data->>'interval_count')::bigint) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "aggregate_usage" text GENERATED ALWAYS AS ((_raw_data->>'aggregate_usage')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "transform_usage" text GENERATED ALWAYS AS ((_raw_data->>'transform_usage')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "trial_period_days" bigint GENERATED ALWAYS AS ((_raw_data->>'trial_period_days')::bigint) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."plans" ADD COLUMN "statement_description" text GENERATED ALWAYS AS ((_raw_data->>'statement_description')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>'active')::boolean) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "nickname" text GENERATED ALWAYS AS ((_raw_data->>'nickname')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "recurring" jsonb GENERATED ALWAYS AS (_raw_data->'recurring') STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" integer GENERATED ALWAYS AS ((_raw_data->>'unit_amount')::integer) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "billing_scheme" text GENERATED ALWAYS AS ((_raw_data->>'billing_scheme')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "lookup_key" text GENERATED ALWAYS AS ((_raw_data->>'lookup_key')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "tiers_mode" text GENERATED ALWAYS AS ((_raw_data->>'tiers_mode')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "transform_quantity" jsonb GENERATED ALWAYS AS (_raw_data->'transform_quantity') STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount_decimal" text GENERATED ALWAYS AS ((_raw_data->>'unit_amount_decimal')::text) STORED; -ALTER TABLE "stripe"."prices" ADD COLUMN "product" text GENERATED ALWAYS AS ((_raw_data->>'product')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "active" boolean GENERATED ALWAYS AS ((_raw_data->>'active')::boolean) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "default_price" text GENERATED ALWAYS AS ((_raw_data->>'default_price')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "name" text GENERATED ALWAYS AS ((_raw_data->>'name')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "images" jsonb GENERATED ALWAYS AS (_raw_data->'images') STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "marketing_features" jsonb GENERATED ALWAYS AS (_raw_data->'marketing_features') STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "package_dimensions" jsonb GENERATED ALWAYS AS (_raw_data->'package_dimensions') STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "shippable" boolean GENERATED ALWAYS AS ((_raw_data->>'shippable')::boolean) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "statement_descriptor" text GENERATED ALWAYS AS ((_raw_data->>'statement_descriptor')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "unit_label" text GENERATED ALWAYS AS ((_raw_data->>'unit_label')::text) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "updated" integer GENERATED ALWAYS AS ((_raw_data->>'updated')::integer) STORED; -ALTER TABLE "stripe"."products" ADD COLUMN "url" text GENERATED ALWAYS AS ((_raw_data->>'url')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "amount" integer GENERATED ALWAYS AS ((_raw_data->>'amount')::integer) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "balance_transaction" text GENERATED ALWAYS AS ((_raw_data->>'balance_transaction')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>'charge')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "currency" text GENERATED ALWAYS AS ((_raw_data->>'currency')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "destination_details" jsonb GENERATED ALWAYS AS (_raw_data->'destination_details') STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>'reason')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "receipt_number" text GENERATED ALWAYS AS ((_raw_data->>'receipt_number')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "source_transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>'source_transfer_reversal')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."refunds" ADD COLUMN "transfer_reversal" text GENERATED ALWAYS AS ((_raw_data->>'transfer_reversal')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "billing_zip" text GENERATED ALWAYS AS ((_raw_data->>'billing_zip')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "charge" text GENERATED ALWAYS AS ((_raw_data->>'charge')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "closed_reason" text GENERATED ALWAYS AS ((_raw_data->>'closed_reason')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address" text GENERATED ALWAYS AS ((_raw_data->>'ip_address')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "ip_address_location" jsonb GENERATED ALWAYS AS (_raw_data->'ip_address_location') STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "open" boolean GENERATED ALWAYS AS ((_raw_data->>'open')::boolean) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "opened_reason" text GENERATED ALWAYS AS ((_raw_data->>'opened_reason')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "payment_intent" text GENERATED ALWAYS AS ((_raw_data->>'payment_intent')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "reason" text GENERATED ALWAYS AS ((_raw_data->>'reason')::text) STORED; -ALTER TABLE "stripe"."reviews" ADD COLUMN "session" text GENERATED ALWAYS AS ((_raw_data->>'session')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "description" text GENERATED ALWAYS AS ((_raw_data->>'description')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "payment_method" text GENERATED ALWAYS AS ((_raw_data->>'payment_method')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "usage" text GENERATED ALWAYS AS ((_raw_data->>'usage')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "cancellation_reason" text GENERATED ALWAYS AS ((_raw_data->>'cancellation_reason')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "latest_attempt" text GENERATED ALWAYS AS ((_raw_data->>'latest_attempt')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "mandate" text GENERATED ALWAYS AS ((_raw_data->>'mandate')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "single_use_mandate" text GENERATED ALWAYS AS ((_raw_data->>'single_use_mandate')::text) STORED; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "on_behalf_of" text GENERATED ALWAYS AS ((_raw_data->>'on_behalf_of')::text) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->'billing_thresholds') STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "deleted" boolean GENERATED ALWAYS AS ((_raw_data->>'deleted')::boolean) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "quantity" integer GENERATED ALWAYS AS ((_raw_data->>'quantity')::integer) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "price" text GENERATED ALWAYS AS ((_raw_data->>'price')::text) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>'subscription')::text) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->'tax_rates') STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>'current_period_end')::integer) STORED; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>'current_period_start')::integer) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "application" text GENERATED ALWAYS AS ((_raw_data->>'application')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>'canceled_at')::integer) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "completed_at" integer GENERATED ALWAYS AS ((_raw_data->>'completed_at')::integer) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "current_phase" jsonb GENERATED ALWAYS AS (_raw_data->'current_phase') STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "default_settings" jsonb GENERATED ALWAYS AS (_raw_data->'default_settings') STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "end_behavior" text GENERATED ALWAYS AS ((_raw_data->>'end_behavior')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "phases" jsonb GENERATED ALWAYS AS (_raw_data->'phases') STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_at" integer GENERATED ALWAYS AS ((_raw_data->>'released_at')::integer) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "released_subscription" text GENERATED ALWAYS AS ((_raw_data->>'released_subscription')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "subscription" text GENERATED ALWAYS AS ((_raw_data->>'subscription')::text) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "test_clock" text GENERATED ALWAYS AS ((_raw_data->>'test_clock')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at_period_end" boolean GENERATED ALWAYS AS ((_raw_data->>'cancel_at_period_end')::boolean) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_end" integer GENERATED ALWAYS AS ((_raw_data->>'current_period_end')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "current_period_start" integer GENERATED ALWAYS AS ((_raw_data->>'current_period_start')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_payment_method" text GENERATED ALWAYS AS ((_raw_data->>'default_payment_method')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "items" jsonb GENERATED ALWAYS AS (_raw_data->'items') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "metadata" jsonb GENERATED ALWAYS AS (_raw_data->'metadata') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_setup_intent" text GENERATED ALWAYS AS ((_raw_data->>'pending_setup_intent')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_update" jsonb GENERATED ALWAYS AS (_raw_data->'pending_update') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "status" text GENERATED ALWAYS AS ((_raw_data->>'status')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "application_fee_percent" double precision GENERATED ALWAYS AS ((_raw_data->>'application_fee_percent')::double precision) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_cycle_anchor" integer GENERATED ALWAYS AS ((_raw_data->>'billing_cycle_anchor')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "billing_thresholds" jsonb GENERATED ALWAYS AS (_raw_data->'billing_thresholds') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "cancel_at" integer GENERATED ALWAYS AS ((_raw_data->>'cancel_at')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "canceled_at" integer GENERATED ALWAYS AS ((_raw_data->>'canceled_at')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "collection_method" text GENERATED ALWAYS AS ((_raw_data->>'collection_method')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "days_until_due" integer GENERATED ALWAYS AS ((_raw_data->>'days_until_due')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_source" text GENERATED ALWAYS AS ((_raw_data->>'default_source')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "default_tax_rates" jsonb GENERATED ALWAYS AS (_raw_data->'default_tax_rates') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "discount" jsonb GENERATED ALWAYS AS (_raw_data->'discount') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "ended_at" integer GENERATED ALWAYS AS ((_raw_data->>'ended_at')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "next_pending_invoice_item_invoice" integer GENERATED ALWAYS AS ((_raw_data->>'next_pending_invoice_item_invoice')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pause_collection" jsonb GENERATED ALWAYS AS (_raw_data->'pause_collection') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "pending_invoice_item_interval" jsonb GENERATED ALWAYS AS (_raw_data->'pending_invoice_item_interval') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "start_date" integer GENERATED ALWAYS AS ((_raw_data->>'start_date')::integer) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "transfer_data" jsonb GENERATED ALWAYS AS (_raw_data->'transfer_data') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_end" jsonb GENERATED ALWAYS AS (_raw_data->'trial_end') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "trial_start" jsonb GENERATED ALWAYS AS (_raw_data->'trial_start') STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "schedule" text GENERATED ALWAYS AS ((_raw_data->>'schedule')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "latest_invoice" text GENERATED ALWAYS AS ((_raw_data->>'latest_invoice')::text) STORED; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "plan" text GENERATED ALWAYS AS ((_raw_data->>'plan')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "object" text GENERATED ALWAYS AS ((_raw_data->>'object')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "country" text GENERATED ALWAYS AS ((_raw_data->>'country')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "customer" text GENERATED ALWAYS AS ((_raw_data->>'customer')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "type" text GENERATED ALWAYS AS ((_raw_data->>'type')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "value" text GENERATED ALWAYS AS ((_raw_data->>'value')::text) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "created" integer GENERATED ALWAYS AS ((_raw_data->>'created')::integer) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "livemode" boolean GENERATED ALWAYS AS ((_raw_data->>'livemode')::boolean) STORED; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "owner" jsonb GENERATED ALWAYS AS (_raw_data->'owner') STORED; - --- ============================================================================ --- STEP 3: RECREATE INDEXES --- ============================================================================ - -CREATE INDEX stripe_active_entitlements_customer_idx ON "stripe"."active_entitlements" USING btree (customer); -CREATE INDEX stripe_active_entitlements_feature_idx ON "stripe"."active_entitlements" USING btree (feature); -CREATE UNIQUE INDEX active_entitlements_lookup_key_key ON "stripe"."active_entitlements" (lookup_key) WHERE lookup_key IS NOT NULL; -CREATE INDEX stripe_checkout_session_line_items_session_idx ON "stripe"."checkout_session_line_items" USING btree (checkout_session); -CREATE INDEX stripe_checkout_session_line_items_price_idx ON "stripe"."checkout_session_line_items" USING btree (price); -CREATE INDEX stripe_checkout_sessions_customer_idx ON "stripe"."checkout_sessions" USING btree (customer); -CREATE INDEX stripe_checkout_sessions_subscription_idx ON "stripe"."checkout_sessions" USING btree (subscription); -CREATE INDEX stripe_checkout_sessions_payment_intent_idx ON "stripe"."checkout_sessions" USING btree (payment_intent); -CREATE INDEX stripe_checkout_sessions_invoice_idx ON "stripe"."checkout_sessions" USING btree (invoice); -CREATE INDEX stripe_credit_notes_customer_idx ON "stripe"."credit_notes" USING btree (customer); -CREATE INDEX stripe_credit_notes_invoice_idx ON "stripe"."credit_notes" USING btree (invoice); -CREATE INDEX stripe_dispute_created_idx ON "stripe"."disputes" USING btree (created); -CREATE INDEX stripe_early_fraud_warnings_charge_idx ON "stripe"."early_fraud_warnings" USING btree (charge); -CREATE INDEX stripe_early_fraud_warnings_payment_intent_idx ON "stripe"."early_fraud_warnings" USING btree (payment_intent); -CREATE UNIQUE INDEX features_lookup_key_key ON "stripe"."features" (lookup_key) WHERE lookup_key IS NOT NULL; -CREATE INDEX stripe_invoices_customer_idx ON "stripe"."invoices" USING btree (customer); -CREATE INDEX stripe_invoices_subscription_idx ON "stripe"."invoices" USING btree (subscription); -CREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer); -CREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice); -CREATE INDEX stripe_payment_methods_customer_idx ON "stripe"."payment_methods" USING btree (customer); -CREATE INDEX stripe_refunds_charge_idx ON "stripe"."refunds" USING btree (charge); -CREATE INDEX stripe_refunds_payment_intent_idx ON "stripe"."refunds" USING btree (payment_intent); -CREATE INDEX stripe_reviews_charge_idx ON "stripe"."reviews" USING btree (charge); -CREATE INDEX stripe_reviews_payment_intent_idx ON "stripe"."reviews" USING btree (payment_intent); -CREATE INDEX stripe_setup_intents_customer_idx ON "stripe"."setup_intents" USING btree (customer); -CREATE INDEX stripe_tax_ids_customer_idx ON "stripe"."tax_ids" USING btree (customer); diff --git a/packages/sync-engine/src/database/migrations/0049_remove_redundant_underscores_from_metadata_tables.sql b/packages/sync-engine/src/database/migrations/0049_remove_redundant_underscores_from_metadata_tables.sql deleted file mode 100644 index 3ae3fff3..00000000 --- a/packages/sync-engine/src/database/migrations/0049_remove_redundant_underscores_from_metadata_tables.sql +++ /dev/null @@ -1,68 +0,0 @@ --- Remove redundant underscore prefixes from columns in metadata tables --- --- For tables that are already prefixed with underscore (indicating they are --- metadata/system tables), the underscore prefix on columns is redundant. --- This migration removes those redundant prefixes to keep naming cleaner. --- --- Affected tables: _sync_status, _managed_webhooks - --- Create a new trigger function for metadata tables that references updated_at without underscore -CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger - LANGUAGE plpgsql -AS $$ -begin - new.updated_at = now(); - return NEW; -end; -$$; - --- Update _sync_status table --- Step 1: Drop constraints and triggers that reference the old column names -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_status"; -ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_account_key; -ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account; -DROP INDEX IF EXISTS "stripe"."idx_sync_status_resource_account"; - --- Step 2: Rename columns -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_id" TO "id"; -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_last_synced_at" TO "last_synced_at"; -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_updated_at" TO "updated_at"; -ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_account_id" TO "account_id"; - --- Step 3: Recreate constraints and trigger with new column names -ALTER TABLE "stripe"."_sync_status" - ADD CONSTRAINT _sync_status_resource_account_key - UNIQUE (resource, "account_id"); - -CREATE INDEX IF NOT EXISTS idx_sync_status_resource_account - ON "stripe"."_sync_status" (resource, "account_id"); - -ALTER TABLE "stripe"."_sync_status" - ADD CONSTRAINT fk_sync_status_account - FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id"); - -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_status" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at_metadata(); - --- Update _managed_webhooks table --- Step 1: Drop constraints and triggers that reference the old column names -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks"; -ALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account; - --- Step 2: Rename columns -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_id" TO "id"; -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_last_synced_at" TO "last_synced_at"; -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_updated_at" TO "updated_at"; -ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_account_id" TO "account_id"; - --- Step 3: Recreate foreign key constraint and trigger with new column names -ALTER TABLE "stripe"."_managed_webhooks" - ADD CONSTRAINT fk_managed_webhooks_account - FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id"); - -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_managed_webhooks" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at_metadata(); diff --git a/packages/sync-engine/src/database/migrations/0050_rename_id_to_match_stripe_api.sql b/packages/sync-engine/src/database/migrations/0050_rename_id_to_match_stripe_api.sql deleted file mode 100644 index 97b1d9e4..00000000 --- a/packages/sync-engine/src/database/migrations/0050_rename_id_to_match_stripe_api.sql +++ /dev/null @@ -1,239 +0,0 @@ --- Rename _id back to id to match Stripe API field names --- --- Migration 0048 added underscore prefixes to all "reserved" columns including id. --- However, id is actually a field that comes directly from the Stripe API and should --- match the API naming for agent/user comprehension. --- --- Additionally, this migration converts id from a regular column to a GENERATED column --- derived from _raw_data->>'id', ensuring the raw_data is the single source of truth. - --- ============================================================================ --- Step 1: Drop all foreign key constraints referencing accounts._id --- ============================================================================ - -ALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS fk_active_entitlements_account; -ALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS fk_charges_account; -ALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS fk_checkout_session_line_items_account; -ALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS fk_checkout_sessions_account; -ALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS fk_credit_notes_account; -ALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS fk_customers_account; -ALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS fk_disputes_account; -ALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS fk_early_fraud_warnings_account; -ALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS fk_features_account; -ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS fk_invoices_account; -ALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account; -ALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS fk_payment_intents_account; -ALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS fk_payment_methods_account; -ALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS fk_plans_account; -ALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS fk_prices_account; -ALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS fk_products_account; -ALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS fk_refunds_account; -ALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS fk_reviews_account; -ALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS fk_setup_intents_account; -ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS fk_subscription_items_account; -ALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS fk_subscription_schedules_account; -ALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS fk_subscriptions_account; -ALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS fk_tax_ids_account; -ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account; - --- ============================================================================ --- Step 2: Convert accounts._id to generated column accounts.id --- ============================================================================ - -ALTER TABLE "stripe"."accounts" DROP CONSTRAINT IF EXISTS accounts_pkey; -ALTER TABLE "stripe"."accounts" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."accounts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."accounts" ADD PRIMARY KEY (id); - --- ============================================================================ --- Step 3: Convert _id to generated column id for all Stripe entity tables --- ============================================================================ - --- active_entitlements -ALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS active_entitlements_pkey; -ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."active_entitlements" ADD PRIMARY KEY (id); - --- charges -ALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS charges_pkey; -ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."charges" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."charges" ADD PRIMARY KEY (id); - --- checkout_session_line_items -ALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS checkout_session_line_items_pkey; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" ADD PRIMARY KEY (id); - --- checkout_sessions -ALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS checkout_sessions_pkey; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."checkout_sessions" ADD PRIMARY KEY (id); - --- credit_notes -ALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS credit_notes_pkey; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."credit_notes" ADD PRIMARY KEY (id); - --- coupons -ALTER TABLE "stripe"."coupons" DROP CONSTRAINT IF EXISTS coupons_pkey; -ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."coupons" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."coupons" ADD PRIMARY KEY (id); - --- customers -ALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS customers_pkey; -ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."customers" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."customers" ADD PRIMARY KEY (id); - --- disputes -ALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS disputes_pkey; -ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."disputes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."disputes" ADD PRIMARY KEY (id); - --- early_fraud_warnings -ALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS early_fraud_warnings_pkey; -ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."early_fraud_warnings" ADD PRIMARY KEY (id); - --- events -ALTER TABLE "stripe"."events" DROP CONSTRAINT IF EXISTS events_pkey; -ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."events" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."events" ADD PRIMARY KEY (id); - --- features -ALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS features_pkey; -ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."features" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."features" ADD PRIMARY KEY (id); - --- invoices -ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS invoices_pkey; -ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."invoices" ADD PRIMARY KEY (id); - --- payment_intents -ALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS payment_intents_pkey; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."payment_intents" ADD PRIMARY KEY (id); - --- payment_methods -ALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS payment_methods_pkey; -ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."payment_methods" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."payment_methods" ADD PRIMARY KEY (id); - --- payouts -ALTER TABLE "stripe"."payouts" DROP CONSTRAINT IF EXISTS payouts_pkey; -ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."payouts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."payouts" ADD PRIMARY KEY (id); - --- plans -ALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS plans_pkey; -ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."plans" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."plans" ADD PRIMARY KEY (id); - --- prices -ALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS prices_pkey; -ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."prices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."prices" ADD PRIMARY KEY (id); - --- products -ALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS products_pkey; -ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."products" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."products" ADD PRIMARY KEY (id); - --- refunds -ALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS refunds_pkey; -ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."refunds" ADD PRIMARY KEY (id); - --- reviews -ALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS reviews_pkey; -ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."reviews" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."reviews" ADD PRIMARY KEY (id); - --- setup_intents -ALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS setup_intents_pkey; -ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."setup_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."setup_intents" ADD PRIMARY KEY (id); - --- subscription_items -ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS subscription_items_pkey; -ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."subscription_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."subscription_items" ADD PRIMARY KEY (id); - --- subscription_schedules -ALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS subscription_schedules_pkey; -ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."subscription_schedules" ADD PRIMARY KEY (id); - --- subscriptions -ALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS subscriptions_pkey; -ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."subscriptions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."subscriptions" ADD PRIMARY KEY (id); - --- tax_ids -ALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS tax_ids_pkey; -ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "_id"; -ALTER TABLE "stripe"."tax_ids" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED; -ALTER TABLE "stripe"."tax_ids" ADD PRIMARY KEY (id); - --- ============================================================================ --- Step 4: Handle metadata tables --- ============================================================================ - --- _managed_webhooks (internal metadata table, doesn't use _raw_data pattern) --- Already uses "id" without underscore (migration 0049), no changes needed - --- _sync_status (internal table, uses auto-incrementing id not from Stripe) --- Already uses "id" without underscore (migration 0049), no changes needed - --- ============================================================================ --- Step 5: Recreate all foreign key constraints --- ============================================================================ - -ALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id); diff --git a/packages/sync-engine/src/database/migrations/0051_remove_webhook_uuid.sql b/packages/sync-engine/src/database/migrations/0051_remove_webhook_uuid.sql deleted file mode 100644 index 14a3b0da..00000000 --- a/packages/sync-engine/src/database/migrations/0051_remove_webhook_uuid.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Remove UUID from managed webhooks --- UUID-based routing is no longer used; webhooks are identified by exact URL match --- Legacy webhooks with UUID in URL will be automatically deleted and recreated - -drop index if exists "stripe"."stripe_managed_webhooks_uuid_idx"; - -alter table "stripe"."_managed_webhooks" drop column if exists "uuid"; diff --git a/packages/sync-engine/src/database/migrations/0052_webhook_url_uniqueness.sql b/packages/sync-engine/src/database/migrations/0052_webhook_url_uniqueness.sql deleted file mode 100644 index f0743b3f..00000000 --- a/packages/sync-engine/src/database/migrations/0052_webhook_url_uniqueness.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Add unique constraint on URL per account to prevent duplicate webhooks at database level --- This prevents race conditions where multiple instances try to create webhooks for the same URL --- Since UUIDs have been removed from URLs, we can enforce strict uniqueness on the URL column per account --- Note: Different accounts can have webhooks with the same URL - -alter table "stripe"."_managed_webhooks" - add constraint managed_webhooks_url_account_unique unique ("url", "account_id"); diff --git a/packages/sync-engine/src/database/migrations/0053_sync_observability.sql b/packages/sync-engine/src/database/migrations/0053_sync_observability.sql deleted file mode 100644 index 6e4ab063..00000000 --- a/packages/sync-engine/src/database/migrations/0053_sync_observability.sql +++ /dev/null @@ -1,104 +0,0 @@ --- Observable Sync System: Track sync runs and individual object syncs --- Enables observability for long-running syncs (days, not minutes) --- --- Two-level hierarchy: --- _sync_run: Parent sync operation (one active per account) --- _sync_obj_run: Individual object syncs within a run --- --- Features: --- - Only one active run per account (EXCLUDE constraint) --- - Configurable object concurrency (max_concurrent) --- - Stale detection (is_stale in dashboard view) --- - Progress tracking per object - --- Step 1: Create _sync_run table (parent sync operation) -CREATE TABLE IF NOT EXISTS "stripe"."_sync_run" ( - "_account_id" TEXT NOT NULL, - started_at TIMESTAMPTZ NOT NULL DEFAULT now(), - status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'complete', 'error')), - max_concurrent INTEGER NOT NULL DEFAULT 3, - completed_at TIMESTAMPTZ, - error_message TEXT, - triggered_by TEXT, - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - PRIMARY KEY ("_account_id", started_at), - - -- Only one active run per account - CONSTRAINT one_active_run_per_account - EXCLUDE ("_account_id" WITH =) WHERE (status = 'running'), - - -- Foreign key to accounts table - CONSTRAINT fk_sync_run_account - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id) -); - --- Step 2: Add updated_at trigger for _sync_run --- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at) -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_run" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at_metadata(); - --- Step 3: Create _sync_obj_run table (individual object syncs) -CREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_run" ( - "_account_id" TEXT NOT NULL, - run_started_at TIMESTAMPTZ NOT NULL, - object TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'complete', 'error')), - started_at TIMESTAMPTZ, - completed_at TIMESTAMPTZ, - processed_count INTEGER DEFAULT 0, - cursor TEXT, - error_message TEXT, - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - PRIMARY KEY ("_account_id", run_started_at, object), - - -- Foreign key to parent sync run - CONSTRAINT fk_sync_obj_run_parent - FOREIGN KEY ("_account_id", run_started_at) REFERENCES "stripe"."_sync_run" ("_account_id", started_at) -); - --- Step 4: Add updated_at trigger for _sync_obj_run --- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at) -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."_sync_obj_run" - FOR EACH ROW - EXECUTE PROCEDURE set_updated_at_metadata(); - --- Step 5: Create indexes for efficient queries -CREATE INDEX IF NOT EXISTS idx_sync_run_account_status - ON "stripe"."_sync_run" ("_account_id", status); - -CREATE INDEX IF NOT EXISTS idx_sync_obj_run_status - ON "stripe"."_sync_obj_run" ("_account_id", run_started_at, status); - --- Step 6: Create sync_dashboard view for observability -CREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS -SELECT - r."_account_id" as account_id, - r.started_at as run_started_at, - r.status as run_status, - r.completed_at as run_completed_at, - r.max_concurrent, - r.triggered_by, - o.object, - o.status as object_status, - o.started_at as object_started_at, - o.completed_at as object_completed_at, - o.processed_count, - o.error_message, - o.updated_at, - -- Duration in seconds - EXTRACT(EPOCH FROM (COALESCE(o.completed_at, now()) - o.started_at))::integer as duration_seconds, - -- Stale detection: running but no update in 5 min - CASE - WHEN o.status = 'running' AND o.updated_at < now() - interval '5 minutes' - THEN true - ELSE false - END as is_stale -FROM "stripe"."_sync_run" r -LEFT JOIN "stripe"."_sync_obj_run" o - ON o."_account_id" = r."_account_id" - AND o.run_started_at = r.started_at; diff --git a/packages/sync-engine/src/database/migrations/0054_drop_sync_status.sql b/packages/sync-engine/src/database/migrations/0054_drop_sync_status.sql deleted file mode 100644 index cade2f5d..00000000 --- a/packages/sync-engine/src/database/migrations/0054_drop_sync_status.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Drop the old _sync_status table --- This table has been replaced by _sync_run and _sync_obj_run for better observability --- See migration 0053_sync_observability.sql - -DROP TABLE IF EXISTS "stripe"."_sync_status"; diff --git a/packages/sync-engine/src/database/migrations/0055_bigint_money_columns.sql b/packages/sync-engine/src/database/migrations/0055_bigint_money_columns.sql deleted file mode 100644 index b759706c..00000000 --- a/packages/sync-engine/src/database/migrations/0055_bigint_money_columns.sql +++ /dev/null @@ -1,72 +0,0 @@ --- Fix generated columns: must drop and recreate with ::bigint cast --- Money columns that can overflow PostgreSQL integer max (~2.1 billion) - --- checkout_session_line_items -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_discount"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_discount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_discount')::bigint) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_subtotal"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_subtotal')::bigint) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_tax"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_tax" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_tax')::bigint) STORED; -ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN "amount_total"; -ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_total')::bigint) STORED; - --- checkout_sessions -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_subtotal"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_subtotal')::bigint) STORED; -ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN "amount_total"; -ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "amount_total" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_total')::bigint) STORED; - --- credit_notes -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "amount_shipping"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "amount_shipping" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_shipping')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "discount_amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "discount_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'discount_amount')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "out_of_band_amount"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "out_of_band_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'out_of_band_amount')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>'subtotal')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "subtotal_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "subtotal_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>'subtotal_excluding_tax')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "total"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total" bigint GENERATED ALWAYS AS ((_raw_data->>'total')::bigint) STORED; -ALTER TABLE "stripe"."credit_notes" DROP COLUMN "total_excluding_tax"; -ALTER TABLE "stripe"."credit_notes" ADD COLUMN "total_excluding_tax" bigint GENERATED ALWAYS AS ((_raw_data->>'total_excluding_tax')::bigint) STORED; - --- customers -ALTER TABLE "stripe"."customers" DROP COLUMN "balance"; -ALTER TABLE "stripe"."customers" ADD COLUMN "balance" bigint GENERATED ALWAYS AS ((_raw_data->>'balance')::bigint) STORED; - --- invoices -ALTER TABLE "stripe"."invoices" DROP COLUMN "ending_balance"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "ending_balance" bigint GENERATED ALWAYS AS ((_raw_data->>'ending_balance')::bigint) STORED; -ALTER TABLE "stripe"."invoices" DROP COLUMN "starting_balance"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "starting_balance" bigint GENERATED ALWAYS AS ((_raw_data->>'starting_balance')::bigint) STORED; -ALTER TABLE "stripe"."invoices" DROP COLUMN "subtotal"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "subtotal" bigint GENERATED ALWAYS AS ((_raw_data->>'subtotal')::bigint) STORED; -ALTER TABLE "stripe"."invoices" DROP COLUMN "tax"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "tax" bigint GENERATED ALWAYS AS ((_raw_data->>'tax')::bigint) STORED; -ALTER TABLE "stripe"."invoices" DROP COLUMN "post_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "post_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'post_payment_credit_notes_amount')::bigint) STORED; -ALTER TABLE "stripe"."invoices" DROP COLUMN "pre_payment_credit_notes_amount"; -ALTER TABLE "stripe"."invoices" ADD COLUMN "pre_payment_credit_notes_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'pre_payment_credit_notes_amount')::bigint) STORED; - --- payment_intents -ALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_capturable"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_capturable" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_capturable')::bigint) STORED; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN "amount_received"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "amount_received" bigint GENERATED ALWAYS AS ((_raw_data->>'amount_received')::bigint) STORED; -ALTER TABLE "stripe"."payment_intents" DROP COLUMN "application_fee_amount"; -ALTER TABLE "stripe"."payment_intents" ADD COLUMN "application_fee_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'application_fee_amount')::bigint) STORED; - --- prices -ALTER TABLE "stripe"."prices" DROP COLUMN "unit_amount"; -ALTER TABLE "stripe"."prices" ADD COLUMN "unit_amount" bigint GENERATED ALWAYS AS ((_raw_data->>'unit_amount')::bigint) STORED; - --- refunds -ALTER TABLE "stripe"."refunds" DROP COLUMN "amount"; -ALTER TABLE "stripe"."refunds" ADD COLUMN "amount" bigint GENERATED ALWAYS AS ((_raw_data->>'amount')::bigint) STORED; diff --git a/packages/sync-engine/src/database/migrations/0056_sync_run_closed_at.sql b/packages/sync-engine/src/database/migrations/0056_sync_run_closed_at.sql deleted file mode 100644 index ed5deced..00000000 --- a/packages/sync-engine/src/database/migrations/0056_sync_run_closed_at.sql +++ /dev/null @@ -1,53 +0,0 @@ --- Add closed_at column to _sync_run --- closed_at IS NULL means the run is still active --- Status is derived from object states when closed_at IS NOT NULL - --- Step 1: Drop dependent view first -DROP VIEW IF EXISTS "stripe"."sync_dashboard"; - --- Step 2: Drop the old constraint, status column, and completed_at column -ALTER TABLE "stripe"."_sync_run" DROP CONSTRAINT IF EXISTS one_active_run_per_account; -ALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS status; -ALTER TABLE "stripe"."_sync_run" DROP COLUMN IF EXISTS completed_at; - --- Step 3: Add closed_at column -ALTER TABLE "stripe"."_sync_run" ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ; - --- Step 4: Create exclusion constraint (only one active run per account) -ALTER TABLE "stripe"."_sync_run" -ADD CONSTRAINT one_active_run_per_account -EXCLUDE ("_account_id" WITH =) WHERE (closed_at IS NULL); - --- Step 5: Recreate sync_dashboard view (run-level only, one row per run) --- Base table: _sync_run (parent sync operation) --- Child table: _sync_obj_run (individual object syncs) -CREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS -SELECT - run."_account_id" as account_id, - run.started_at, - run.closed_at, - run.max_concurrent, - run.triggered_by, - run.updated_at, - -- Derived status from object states - CASE - WHEN run.closed_at IS NULL THEN 'running' - WHEN EXISTS ( - SELECT 1 FROM "stripe"."_sync_obj_run" obj - WHERE obj."_account_id" = run."_account_id" - AND obj.run_started_at = run.started_at - AND obj.status = 'error' - ) THEN 'error' - ELSE 'complete' - END as status, - -- First error message from failed objects - (SELECT obj.error_message FROM "stripe"."_sync_obj_run" obj - WHERE obj."_account_id" = run."_account_id" - AND obj.run_started_at = run.started_at - AND obj.status = 'error' - ORDER BY obj.object LIMIT 1) as error_message, - -- Total processed count across all objects - COALESCE((SELECT SUM(obj.processed_count) FROM "stripe"."_sync_obj_run" obj - WHERE obj."_account_id" = run."_account_id" - AND obj.run_started_at = run.started_at), 0) as processed_count -FROM "stripe"."_sync_run" run; diff --git a/packages/sync-engine/src/database/migrations/0057_rename_sync_tables.sql b/packages/sync-engine/src/database/migrations/0057_rename_sync_tables.sql deleted file mode 100644 index 7d0ae8a5..00000000 --- a/packages/sync-engine/src/database/migrations/0057_rename_sync_tables.sql +++ /dev/null @@ -1,57 +0,0 @@ --- Rename sync observability tables and create public sync_runs view --- Internal tables use _ prefix, public view is sync_runs - --- Step 1: Drop the old sync_dashboard view -DROP VIEW IF EXISTS "stripe"."sync_dashboard"; - --- Step 2: Rename tables to plural (keep _ prefix for internal tables) -ALTER TABLE "stripe"."_sync_run" RENAME TO "_sync_runs"; -ALTER TABLE "stripe"."_sync_obj_run" RENAME TO "_sync_obj_runs"; - --- Step 3: Update foreign key constraint name -ALTER TABLE "stripe"."_sync_obj_runs" - DROP CONSTRAINT IF EXISTS fk_sync_obj_run_parent; - -ALTER TABLE "stripe"."_sync_obj_runs" - ADD CONSTRAINT fk_sync_obj_runs_parent - FOREIGN KEY ("_account_id", run_started_at) - REFERENCES "stripe"."_sync_runs" ("_account_id", started_at); - --- Step 4: Recreate indexes with new table names -DROP INDEX IF EXISTS "stripe"."idx_sync_run_account_status"; -DROP INDEX IF EXISTS "stripe"."idx_sync_obj_run_status"; - -CREATE INDEX idx_sync_runs_account_status - ON "stripe"."_sync_runs" ("_account_id", closed_at); - -CREATE INDEX idx_sync_obj_runs_status - ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status); - --- Step 5: Create public sync_runs view (one row per run with aggregates) -CREATE VIEW "stripe"."sync_runs" AS -SELECT - r._account_id as account_id, - r.started_at, - r.closed_at, - r.triggered_by, - r.max_concurrent, - -- Aggregate metrics from child objects - COALESCE(SUM(o.processed_count), 0) as total_processed, - COUNT(o.*) as total_objects, - COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, - COUNT(*) FILTER (WHERE o.status = 'error') as error_count, - COUNT(*) FILTER (WHERE o.status = 'running') as running_count, - COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, - -- Collect error messages if any - STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, - -- Derive overall status from run state and object states - CASE - WHEN r.closed_at IS NULL THEN 'running' - WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' - ELSE 'complete' - END as status -FROM "stripe"."_sync_runs" r -LEFT JOIN "stripe"."_sync_obj_runs" o - ON o._account_id = r._account_id - AND o.run_started_at = r.started_at -GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; diff --git a/packages/sync-engine/src/database/migrations/0058_improve_sync_runs_status.sql b/packages/sync-engine/src/database/migrations/0058_improve_sync_runs_status.sql deleted file mode 100644 index bdc43ffe..00000000 --- a/packages/sync-engine/src/database/migrations/0058_improve_sync_runs_status.sql +++ /dev/null @@ -1,36 +0,0 @@ --- Improve sync_runs view status logic --- More granular status based on actual object run states - -DROP VIEW IF EXISTS "stripe"."sync_runs"; - -CREATE VIEW "stripe"."sync_runs" AS -SELECT - r._account_id as account_id, - r.started_at, - r.closed_at, - r.triggered_by, - r.max_concurrent, - -- Aggregate metrics from child objects - COALESCE(SUM(o.processed_count), 0) as total_processed, - COUNT(o.*) as total_objects, - COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, - COUNT(*) FILTER (WHERE o.status = 'error') as error_count, - COUNT(*) FILTER (WHERE o.status = 'running') as running_count, - COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, - -- Collect error messages if any - STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, - -- Derive overall status from run state and object states - CASE - -- Run still open (closed_at IS NULL) - WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' - WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' - WHEN r.closed_at IS NULL THEN 'running' - -- Run closed (closed_at IS NOT NULL) - WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' - ELSE 'complete' - END as status -FROM "stripe"."_sync_runs" r -LEFT JOIN "stripe"."_sync_obj_runs" o - ON o._account_id = r._account_id - AND o.run_started_at = r.started_at -GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; diff --git a/packages/sync-engine/src/database/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql b/packages/sync-engine/src/database/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql deleted file mode 100644 index ffe5d0f8..00000000 --- a/packages/sync-engine/src/database/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql +++ /dev/null @@ -1,61 +0,0 @@ --- event_timestamp and event_type are not generated columns because they are not immutable. --- Postgres requires generated expressions to be immutable. - -CREATE TABLE IF NOT EXISTS "stripe"."subscription_item_change_events_v2_beta" ( - "_raw_data" jsonb NOT NULL, - "_last_synced_at" timestamptz, - "_updated_at" timestamptz DEFAULT now(), - "_account_id" text NOT NULL, - - "event_timestamp" timestamptz NOT NULL, - "event_type" text NOT NULL, - "subscription_item_id" text NOT NULL, - - PRIMARY KEY ("_account_id", "event_timestamp", "event_type", "subscription_item_id") -); - --- Foreign key to stripe.accounts -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - DROP CONSTRAINT IF EXISTS fk_subscription_item_change_events_v2_beta_account; -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD CONSTRAINT fk_subscription_item_change_events_v2_beta_account - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); - --- Maintain _updated_at on UPDATE -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."subscription_item_change_events_v2_beta"; -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."subscription_item_change_events_v2_beta" - FOR EACH ROW EXECUTE FUNCTION set_updated_at(); - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "currency" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'currency', ''))::text) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "mrr_change" bigint - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'mrr_change', ''))::bigint) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "quantity_change" bigint - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'quantity_change', ''))::bigint) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "subscription_id" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'subscription_id', ''))::text) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "customer_id" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'customer_id', ''))::text) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "price_id" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'price_id', ''))::text) STORED; - -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "product_id" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'product_id', ''))::text) STORED; - --- Keep as text to avoid non-immutable timestamp casts in a generated column -ALTER TABLE "stripe"."subscription_item_change_events_v2_beta" - ADD COLUMN IF NOT EXISTS "local_event_timestamp" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'local_event_timestamp', ''))::text) STORED; \ No newline at end of file diff --git a/packages/sync-engine/src/database/migrations/0060_sigma_exchange_rates_from_usd.sql b/packages/sync-engine/src/database/migrations/0060_sigma_exchange_rates_from_usd.sql deleted file mode 100644 index b4a6e74a..00000000 --- a/packages/sync-engine/src/database/migrations/0060_sigma_exchange_rates_from_usd.sql +++ /dev/null @@ -1,38 +0,0 @@ - -CREATE TABLE IF NOT EXISTS "stripe"."exchange_rates_from_usd" ( - "_raw_data" jsonb NOT NULL, - "_last_synced_at" timestamptz, - "_updated_at" timestamptz DEFAULT now(), - "_account_id" text NOT NULL, - - "date" date NOT NULL, - "sell_currency" text NOT NULL, - - PRIMARY KEY ("_account_id", "date", "sell_currency") -); - --- Foreign key to stripe.accounts -ALTER TABLE "stripe"."exchange_rates_from_usd" - DROP CONSTRAINT IF EXISTS fk_exchange_rates_from_usd_account; -ALTER TABLE "stripe"."exchange_rates_from_usd" - ADD CONSTRAINT fk_exchange_rates_from_usd_account - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); - --- Maintain _updated_at on UPDATE -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."exchange_rates_from_usd"; -CREATE TRIGGER handle_updated_at - BEFORE UPDATE ON "stripe"."exchange_rates_from_usd" - FOR EACH ROW EXECUTE FUNCTION set_updated_at(); - -ALTER TABLE "stripe"."exchange_rates_from_usd" - ADD COLUMN IF NOT EXISTS "buy_currency_exchange_rates" text - GENERATED ALWAYS AS ((NULLIF(_raw_data->>'buy_currency_exchange_rates', ''))::text) STORED; - --- Index on date for efficient range queries -CREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_date - ON "stripe"."exchange_rates_from_usd" ("date"); - --- Index on sell_currency for filtering by currency -CREATE INDEX IF NOT EXISTS idx_exchange_rates_from_usd_sell_currency - ON "stripe"."exchange_rates_from_usd" ("sell_currency"); - diff --git a/packages/sync-engine/src/database/migrations/0061_add_page_cursor.sql b/packages/sync-engine/src/database/migrations/0061_add_page_cursor.sql deleted file mode 100644 index 9384e685..00000000 --- a/packages/sync-engine/src/database/migrations/0061_add_page_cursor.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add page_cursor column for pagination state within a single sync run. --- This is used to store the starting_after ID for backfills using Stripe list calls. -ALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS page_cursor text; diff --git a/packages/sync-engine/src/database/migrations/0062_sigma_query_runs.sql b/packages/sync-engine/src/database/migrations/0062_sigma_query_runs.sql deleted file mode 100644 index 3def6f10..00000000 --- a/packages/sync-engine/src/database/migrations/0062_sigma_query_runs.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Allow parallel sync runs per triggered_by (sigma-worker vs stripe-worker) -ALTER TABLE "stripe"."_sync_runs" DROP CONSTRAINT IF EXISTS one_active_run_per_account; -ALTER TABLE "stripe"."_sync_runs" -ADD CONSTRAINT one_active_run_per_account_triggered_by -EXCLUDE ( - "_account_id" WITH =, - COALESCE(triggered_by, 'default') WITH = -) WHERE (closed_at IS NULL); diff --git a/packages/sync-engine/src/database/migrations/0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql b/packages/sync-engine/src/database/migrations/0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql deleted file mode 100644 index 24c08796..00000000 --- a/packages/sync-engine/src/database/migrations/0063_drop_sigma_subscription_item_and_exchange_rate_tables.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Drop unused sigma beta tables if they exist. -DROP TABLE IF EXISTS "stripe"."subscription_item_change_events_v2_beta"; -DROP TABLE IF EXISTS "stripe"."exchange_rates_from_usd"; diff --git a/packages/sync-engine/src/database/migrations/0064_add_created_gte_lte.sql b/packages/sync-engine/src/database/migrations/0064_add_created_gte_lte.sql deleted file mode 100644 index 483ecd9a..00000000 --- a/packages/sync-engine/src/database/migrations/0064_add_created_gte_lte.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Add created_gte / created_lte columns for time-range partitioned parallel sync. --- Workers use these to scope their Stripe list calls to a specific created window. --- Stored as Unix epoch seconds (INTEGER) to match Stripe's created filter format. --- created_gte defaults to 0 for non-chunked rows (required by PK). -ALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_gte INTEGER NOT NULL DEFAULT 0; -ALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_lte INTEGER; - --- Expand PK to include created_gte so multiple time-range chunks of the same object can coexist. --- PK constraint kept original name from 0053 (_sync_obj_run_pkey) after table rename in 0057. -ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey"; -ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_run_pkey"; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte); diff --git a/packages/sync-engine/src/database/migrations/0065_add_created_lte_to_pk.sql b/packages/sync-engine/src/database/migrations/0065_add_created_lte_to_pk.sql deleted file mode 100644 index a0250823..00000000 --- a/packages/sync-engine/src/database/migrations/0065_add_created_lte_to_pk.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Include created_lte in the PK so chunks with the same created_gte but --- different created_lte can coexist. Requires a NOT NULL default first. -ALTER TABLE "stripe"."_sync_obj_runs" - ALTER COLUMN created_lte SET DEFAULT 0; - -UPDATE "stripe"."_sync_obj_runs" - SET created_lte = 0 - WHERE created_lte IS NULL; - -ALTER TABLE "stripe"."_sync_obj_runs" - ALTER COLUMN created_lte SET NOT NULL; - -ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey"; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte, created_lte); diff --git a/packages/sync-engine/src/database/migrations/0066_rate_limits.sql b/packages/sync-engine/src/database/migrations/0066_rate_limits.sql deleted file mode 100644 index c4336ce3..00000000 --- a/packages/sync-engine/src/database/migrations/0066_rate_limits.sql +++ /dev/null @@ -1,43 +0,0 @@ --- Rate limiting table and function for cross-process request throttling. --- Used by claimNextTask to cap how many claims/sec hit the database. - -CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( - key TEXT PRIMARY KEY, - count INTEGER NOT NULL DEFAULT 0, - window_start TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( - rate_key TEXT, - max_requests INTEGER, - window_seconds INTEGER -) -RETURNS VOID AS $$ -DECLARE - now TIMESTAMPTZ := clock_timestamp(); - window_length INTERVAL := make_interval(secs => window_seconds); - current_count INTEGER; -BEGIN - PERFORM pg_advisory_xact_lock(hashtext(rate_key)); - - INSERT INTO "stripe"."_rate_limits" (key, count, window_start) - VALUES (rate_key, 1, now) - ON CONFLICT (key) DO UPDATE - SET count = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN 1 - ELSE "_rate_limits".count + 1 - END, - window_start = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN now - ELSE "_rate_limits".window_start - END; - - SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; - - IF current_count > max_requests THEN - RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; - END IF; -END; -$$ LANGUAGE plpgsql; diff --git a/packages/sync-engine/src/database/migrations/0067_add_priority_to_sync_obj_runs.sql b/packages/sync-engine/src/database/migrations/0067_add_priority_to_sync_obj_runs.sql deleted file mode 100644 index 570838a5..00000000 --- a/packages/sync-engine/src/database/migrations/0067_add_priority_to_sync_obj_runs.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Add priority column to _sync_obj_runs for deterministic task ordering. --- Priority mirrors the `order` field from resourceRegistry so workers --- always process parent resources (products, prices) before children --- (subscriptions, invoices). - -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0; - -CREATE INDEX IF NOT EXISTS idx_sync_obj_runs_priority - ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status, priority); diff --git a/packages/sync-engine/src/database/migrations/0068_sync_obj_progress_view.sql b/packages/sync-engine/src/database/migrations/0068_sync_obj_progress_view.sql deleted file mode 100644 index a92ccbc9..00000000 --- a/packages/sync-engine/src/database/migrations/0068_sync_obj_progress_view.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Per-object sync progress view for monitoring. --- Defaults to the newest run per account; callers can filter by a specific --- run_started_at if needed. - -DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ); - -CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS -SELECT - r."_account_id" AS account_id, - r.run_started_at, - r.object, - ROUND( - 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), - 1 - ) AS pct_complete, - COALESCE(SUM(r.processed_count), 0) AS processed -FROM "stripe"."_sync_obj_runs" r -WHERE r.run_started_at = ( - SELECT MAX(s.started_at) - FROM "stripe"."_sync_runs" s - WHERE s."_account_id" = r."_account_id" -) -GROUP BY r."_account_id", r.run_started_at, r.object; diff --git a/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql b/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql deleted file mode 100644 index 7efd2d50..00000000 --- a/packages/sync-engine/src/database/migrations/0069_internal_sync_schema.sql +++ /dev/null @@ -1,245 +0,0 @@ --- Internal sync metadata schema bootstrap for OpenAPI runtime. --- Uses idempotent DDL so it can be safely re-run. - -CREATE EXTENSION IF NOT EXISTS btree_gist; - -CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger - LANGUAGE plpgsql -AS $$ -BEGIN - -- Support both legacy "updated_at" and newer "_updated_at" columns. - -- jsonb_populate_record silently ignores keys that are not present on NEW. - NEW := jsonb_populate_record( - NEW, - jsonb_build_object( - 'updated_at', now(), - '_updated_at', now() - ) - ); - RETURN NEW; -END; -$$; - -CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger - LANGUAGE plpgsql -AS $$ -BEGIN - NEW.updated_at = now(); - RETURN NEW; -END; -$$; - -CREATE TABLE IF NOT EXISTS "stripe"."accounts" ( - "_raw_data" jsonb NOT NULL, - "id" text GENERATED ALWAYS AS ((_raw_data->>'id')::text) STORED, - "api_key_hashes" text[] NOT NULL DEFAULT '{}', - "first_synced_at" timestamptz NOT NULL DEFAULT now(), - "_last_synced_at" timestamptz NOT NULL DEFAULT now(), - "_updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("id") -); -CREATE INDEX IF NOT EXISTS "idx_accounts_api_key_hashes" - ON "stripe"."accounts" USING GIN ("api_key_hashes"); -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."accounts"; -CREATE TRIGGER handle_updated_at -BEFORE UPDATE ON "stripe"."accounts" -FOR EACH ROW EXECUTE FUNCTION set_updated_at(); - -CREATE TABLE IF NOT EXISTS "stripe"."_managed_webhooks" ( - "id" text PRIMARY KEY, - "object" text, - "url" text NOT NULL, - "enabled_events" jsonb NOT NULL, - "description" text, - "enabled" boolean, - "livemode" boolean, - "metadata" jsonb, - "secret" text NOT NULL, - "status" text, - "api_version" text, - "created" bigint, - "last_synced_at" timestamptz, - "updated_at" timestamptz NOT NULL DEFAULT now(), - "account_id" text NOT NULL -); -ALTER TABLE "stripe"."_managed_webhooks" - DROP CONSTRAINT IF EXISTS "managed_webhooks_url_account_unique"; -ALTER TABLE "stripe"."_managed_webhooks" - ADD CONSTRAINT "managed_webhooks_url_account_unique" UNIQUE ("url", "account_id"); -ALTER TABLE "stripe"."_managed_webhooks" - DROP CONSTRAINT IF EXISTS "fk_managed_webhooks_account"; -ALTER TABLE "stripe"."_managed_webhooks" - ADD CONSTRAINT "fk_managed_webhooks_account" - FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id); -CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_status" - ON "stripe"."_managed_webhooks" ("status"); -CREATE INDEX IF NOT EXISTS "idx_managed_webhooks_enabled" - ON "stripe"."_managed_webhooks" ("enabled"); -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks"; -CREATE TRIGGER handle_updated_at -BEFORE UPDATE ON "stripe"."_managed_webhooks" -FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); - -CREATE TABLE IF NOT EXISTS "stripe"."_sync_runs" ( - "_account_id" text NOT NULL, - "started_at" timestamptz NOT NULL DEFAULT now(), - "closed_at" timestamptz, - "max_concurrent" integer NOT NULL DEFAULT 3, - "triggered_by" text, - "error_message" text, - "updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("_account_id", "started_at") -); -ALTER TABLE "stripe"."_sync_runs" - ADD COLUMN IF NOT EXISTS "error_message" text; -ALTER TABLE "stripe"."_sync_runs" - DROP CONSTRAINT IF EXISTS "fk_sync_runs_account"; -ALTER TABLE "stripe"."_sync_runs" - ADD CONSTRAINT "fk_sync_runs_account" - FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id); -ALTER TABLE "stripe"."_sync_runs" - DROP CONSTRAINT IF EXISTS one_active_run_per_account; -ALTER TABLE "stripe"."_sync_runs" - DROP CONSTRAINT IF EXISTS one_active_run_per_account_triggered_by; -ALTER TABLE "stripe"."_sync_runs" - ADD CONSTRAINT one_active_run_per_account_triggered_by - EXCLUDE ( - "_account_id" WITH =, - COALESCE(triggered_by, 'default') WITH = - ) WHERE (closed_at IS NULL); -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_runs"; -CREATE TRIGGER handle_updated_at -BEFORE UPDATE ON "stripe"."_sync_runs" -FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); -CREATE INDEX IF NOT EXISTS "idx_sync_runs_account_status" - ON "stripe"."_sync_runs" ("_account_id", "closed_at"); - -CREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_runs" ( - "_account_id" text NOT NULL, - "run_started_at" timestamptz NOT NULL, - "object" text NOT NULL, - "status" text NOT NULL DEFAULT 'pending' - CHECK (status IN ('pending', 'running', 'complete', 'error')), - "started_at" timestamptz, - "completed_at" timestamptz, - "processed_count" integer NOT NULL DEFAULT 0, - "cursor" text, - "page_cursor" text, - "created_gte" integer NOT NULL DEFAULT 0, - "created_lte" integer NOT NULL DEFAULT 0, - "priority" integer NOT NULL DEFAULT 0, - "error_message" text, - "updated_at" timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY ("_account_id", "run_started_at", "object", "created_gte", "created_lte") -); -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS "page_cursor" text; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS "created_gte" integer NOT NULL DEFAULT 0; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS "created_lte" integer NOT NULL DEFAULT 0; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS "priority" integer NOT NULL DEFAULT 0; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD COLUMN IF NOT EXISTS "error_message" text; -ALTER TABLE "stripe"."_sync_obj_runs" - DROP CONSTRAINT IF EXISTS "fk_sync_obj_runs_parent"; -ALTER TABLE "stripe"."_sync_obj_runs" - ADD CONSTRAINT "fk_sync_obj_runs_parent" - FOREIGN KEY ("_account_id", "run_started_at") - REFERENCES "stripe"."_sync_runs" ("_account_id", "started_at"); -DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_obj_runs"; -CREATE TRIGGER handle_updated_at -BEFORE UPDATE ON "stripe"."_sync_obj_runs" -FOR EACH ROW EXECUTE FUNCTION set_updated_at_metadata(); -CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_status" - ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status"); -CREATE INDEX IF NOT EXISTS "idx_sync_obj_runs_priority" - ON "stripe"."_sync_obj_runs" ("_account_id", "run_started_at", "status", "priority"); - -CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" ( - key TEXT PRIMARY KEY, - count INTEGER NOT NULL DEFAULT 0, - window_start TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE OR REPLACE FUNCTION "stripe".check_rate_limit( - rate_key TEXT, - max_requests INTEGER, - window_seconds INTEGER -) -RETURNS VOID AS $$ -DECLARE - now TIMESTAMPTZ := clock_timestamp(); - window_length INTERVAL := make_interval(secs => window_seconds); - current_count INTEGER; -BEGIN - PERFORM pg_advisory_xact_lock(hashtext(rate_key)); - - INSERT INTO "stripe"."_rate_limits" (key, count, window_start) - VALUES (rate_key, 1, now) - ON CONFLICT (key) DO UPDATE - SET count = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN 1 - ELSE "_rate_limits".count + 1 - END, - window_start = CASE - WHEN "_rate_limits".window_start + window_length <= now - THEN now - ELSE "_rate_limits".window_start - END; - - SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key; - - IF current_count > max_requests THEN - RAISE EXCEPTION 'Rate limit exceeded for %', rate_key; - END IF; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE VIEW "stripe"."sync_runs" AS -SELECT - r._account_id as account_id, - r.started_at, - r.closed_at, - r.triggered_by, - r.max_concurrent, - COALESCE(SUM(o.processed_count), 0) as total_processed, - COUNT(o.*) as total_objects, - COUNT(*) FILTER (WHERE o.status = 'complete') as complete_count, - COUNT(*) FILTER (WHERE o.status = 'error') as error_count, - COUNT(*) FILTER (WHERE o.status = 'running') as running_count, - COUNT(*) FILTER (WHERE o.status = 'pending') as pending_count, - STRING_AGG(o.error_message, '; ') FILTER (WHERE o.error_message IS NOT NULL) as error_message, - CASE - WHEN r.closed_at IS NULL AND COUNT(*) FILTER (WHERE o.status = 'running') > 0 THEN 'running' - WHEN r.closed_at IS NULL AND (COUNT(o.*) = 0 OR COUNT(o.*) = COUNT(*) FILTER (WHERE o.status = 'pending')) THEN 'pending' - WHEN r.closed_at IS NULL THEN 'running' - WHEN COUNT(*) FILTER (WHERE o.status = 'error') > 0 THEN 'error' - ELSE 'complete' - END as status -FROM "stripe"."_sync_runs" r -LEFT JOIN "stripe"."_sync_obj_runs" o - ON o._account_id = r._account_id - AND o.run_started_at = r.started_at -GROUP BY r._account_id, r.started_at, r.closed_at, r.triggered_by, r.max_concurrent; - -DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ); -CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS -SELECT - r."_account_id" AS account_id, - r.run_started_at, - r.object, - ROUND( - 100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0), - 1 - ) AS pct_complete, - COALESCE(SUM(r.processed_count), 0) AS processed -FROM "stripe"."_sync_obj_runs" r -WHERE r.run_started_at = ( - SELECT MAX(s.started_at) - FROM "stripe"."_sync_runs" s - WHERE s."_account_id" = r."_account_id" -) -GROUP BY r."_account_id", r.run_started_at, r.object;