/** * @param { import("knex").Knex } knex * @returns { Promise } */ exports.up = async function(knex) { // Guarded: with transaction:false a failure after this CREATE (e.g. in the // Citus distribution below) leaves the table behind on retry. if (!(await knex.schema.hasTable('asset_facts'))) { await knex.schema.createTable('asset_facts', (table) => { table.uuid('tenant').notNullable().references('tenant').inTable('tenants'); table.uuid('asset_fact_id').defaultTo(knex.raw('gen_random_uuid()')).notNullable(); table.uuid('asset_id').notNullable(); table.text('source_type').notNullable(); table.text('provider').nullable(); table.uuid('integration_id').nullable(); table.text('namespace').notNullable(); table.text('fact_key').notNullable(); table.text('label').notNullable(); table.text('value_text').nullable(); table.decimal('value_number', 12, 4).nullable(); table.boolean('value_bool').nullable(); table.jsonb('value_json').notNullable().defaultTo(knex.raw(`'{}'::jsonb`)); table.text('source').notNullable(); table.timestamp('source_updated_at', { useTz: true }).nullable(); table.timestamp('last_synced_at', { useTz: true }).nullable(); table.boolean('is_available').notNullable().defaultTo(true); table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); table.primary(['tenant', 'asset_fact_id']); table.foreign(['tenant', 'asset_id']).references(['tenant', 'asset_id']).inTable('assets').onDelete('CASCADE'); table.index(['tenant', 'asset_id'], 'asset_facts_tenant_asset_idx'); table.unique(['tenant', 'asset_id', 'source_type', 'namespace', 'fact_key'], 'asset_facts_tenant_asset_fact_current_uk'); }); } const citusEnabled = await knex.raw(` SELECT EXISTS ( SELECT 1 FROM pg_extension WHERE extname = 'citus' ) AS enabled; `); if (citusEnabled.rows?.[0]?.enabled) { // asset_facts colocates with assets, which fresh Citus chains never // distributed. No-op on clusters that already have it. const assetsDistributed = await knex.raw(` SELECT EXISTS ( SELECT 1 FROM pg_dist_partition WHERE logicalrelid = 'assets'::regclass ) AS is_distributed; `); if (!assetsDistributed.rows?.[0]?.is_distributed) { await knex.raw("SELECT create_distributed_table('assets', 'tenant')"); } const alreadyDistributed = await knex.raw(` SELECT EXISTS ( SELECT 1 FROM pg_dist_partition WHERE logicalrelid = 'asset_facts'::regclass ) AS is_distributed; `); if (!alreadyDistributed.rows?.[0]?.is_distributed) { await knex.raw("SELECT create_distributed_table('asset_facts', 'tenant', colocate_with => 'assets')"); } } else { console.warn('[create_asset_facts_table] Skipping create_distributed_table (Citus extension unavailable)'); } }; /** * @param { import("knex").Knex } knex * @returns { Promise } */ exports.down = async function(knex) { await knex.schema.dropTableIfExists('asset_facts'); }; // create_distributed_table cannot run inside a transaction block. exports.config = { transaction: false };