-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: contract log query #1601
base: develop
Are you sure you want to change the base?
feat: contract log query #1601
Changes from 10 commits
0330a51
81e8490
838fa4e
ab31da6
565a26f
65cba05
3829a1e
1b6ae00
eba8472
874ad8b
6d94c0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */ | ||
exports.up = async pgm => { | ||
pgm.addColumn('contract_logs', { | ||
value_json: { | ||
type: 'jsonb', | ||
}, | ||
}); | ||
} | ||
|
||
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */ | ||
exports.down = pgm => { | ||
pgm.dropIndex('contract_logs', 'value_json_path_ops_idx'); | ||
pgm.dropColumn('contract_logs', 'value_json'); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1379,6 +1379,7 @@ export interface SmartContractEventInsertValues { | |
contract_identifier: string; | ||
topic: string; | ||
value: PgBytea; | ||
value_json: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be best if we set this as |
||
} | ||
|
||
export interface BurnchainRewardInsertValues { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,16 @@ | ||
import * as path from 'path'; | ||
import PgMigrate, { RunnerOption } from 'node-pg-migrate'; | ||
import { Client } from 'pg'; | ||
import { APP_DIR, isDevEnv, isTestEnv, logError, logger, REPO_DIR } from '../helpers'; | ||
import { Client, QueryResultRow } from 'pg'; | ||
import * as PgCursor from 'pg-cursor'; | ||
import { | ||
APP_DIR, | ||
clarityValueToCompactJson, | ||
isDevEnv, | ||
isTestEnv, | ||
logError, | ||
logger, | ||
REPO_DIR, | ||
} from '../helpers'; | ||
import { getPgClientConfig, PgClientConfig } from './connection-legacy'; | ||
import { connectPostgres, PgServer } from './connection'; | ||
import { databaseHasData } from './event-requests'; | ||
|
@@ -43,6 +52,7 @@ export async function runMigrations( | |
runnerOpts.schema = clientConfig.schema; | ||
} | ||
await PgMigrate(runnerOpts); | ||
await completeSqlMigrations(client, clientConfig); | ||
} catch (error) { | ||
logError(`Error running pg-migrate`, error); | ||
throw error; | ||
|
@@ -99,3 +109,98 @@ export async function dangerousDropAllTables(opts?: { | |
await sql.end(); | ||
} | ||
} | ||
|
||
// Function to finish running sql migrations that are too complex for the node-pg-migrate library. | ||
async function completeSqlMigrations(client: Client, clientConfig: PgClientConfig) { | ||
try { | ||
await client.query('BEGIN'); | ||
await complete_1680181889941_contract_log_json(client, clientConfig); | ||
await client.query('COMMIT'); | ||
} catch (error) { | ||
await client.query('ROLLBACK'); | ||
throw error; | ||
} | ||
} | ||
|
||
async function* pgCursorQuery<R extends QueryResultRow = any>(args: { | ||
clientConfig: PgClientConfig; | ||
queryText: string; | ||
queryValues?: any[]; | ||
batchSize: number; | ||
}) { | ||
const cursorClient = new Client(args.clientConfig); | ||
try { | ||
await cursorClient.connect(); | ||
const cursor = new PgCursor<R>(args.queryText, args.queryValues); | ||
const cursorQuery = cursorClient.query(cursor); | ||
let rows: R[] = []; | ||
do { | ||
rows = await new Promise((resolve, reject) => { | ||
cursorQuery.read(args.batchSize, (error, rows) => (error ? reject(error) : resolve(rows))); | ||
}); | ||
yield* rows; | ||
} while (rows.length > 0); | ||
} finally { | ||
await cursorClient.end(); | ||
} | ||
} | ||
|
||
async function complete_1680181889941_contract_log_json( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a pressing issue that would require us to ship this feature with this kind of migration? This code looks good to me (and I like this experimentation as you specified in the PR desc), but I worry that it could create some problems if we want to add newer migrations that rely on the new columns or something. IMO it would be easier and more maintainable to just ship this as a regular new migration and rely on replay, or to explicitly mark this code as a "patch" somehow which we could drop once we switch to a new v8 release. What do you think? |
||
client: Client, | ||
clientConfig: PgClientConfig | ||
) { | ||
// Determine if this migration has already been run by checking if the bew column is nullable. | ||
const result = await client.query(` | ||
SELECT is_nullable FROM information_schema.columns | ||
WHERE table_name = 'contract_logs' AND column_name = 'value_json' | ||
`); | ||
const migrationNeeded = result.rows[0].is_nullable === 'YES'; | ||
if (!migrationNeeded) { | ||
return; | ||
} | ||
logger.info(`Running migration 1680181889941_contract_log_json..`); | ||
|
||
const contractLogsCursor = pgCursorQuery<{ id: string; value: string }>({ | ||
clientConfig, | ||
queryText: 'SELECT id, value FROM contract_logs', | ||
batchSize: 1000, | ||
}); | ||
|
||
const rowCountQuery = await client.query<{ count: number }>( | ||
'SELECT COUNT(*)::integer FROM contract_logs' | ||
); | ||
const totalRowCount = rowCountQuery.rows[0].count; | ||
let rowsProcessed = 0; | ||
let lastPercentComplete = 0; | ||
const percentLogInterval = 3; | ||
|
||
for await (const row of contractLogsCursor) { | ||
const clarityValJson = JSON.stringify(clarityValueToCompactJson(row.value)); | ||
await client.query({ | ||
name: 'update_contract_log_json', | ||
text: 'UPDATE contract_logs SET value_json = $1 WHERE id = $2', | ||
values: [clarityValJson, row.id], | ||
}); | ||
rowsProcessed++; | ||
const percentComplete = Math.round((rowsProcessed / totalRowCount) * 100); | ||
if (percentComplete > lastPercentComplete + percentLogInterval) { | ||
lastPercentComplete = percentComplete; | ||
logger.info(`Running migration 1680181889941_contract_log_json.. ${percentComplete}%`); | ||
} | ||
} | ||
|
||
logger.info(`Running migration 1680181889941_contract_log_json.. set NOT NULL`); | ||
await client.query(`ALTER TABLE contract_logs ALTER COLUMN value_json SET NOT NULL`); | ||
|
||
logger.info('Running migration 1680181889941_contract_log_json.. creating jsonb_path_ops index'); | ||
await client.query( | ||
`CREATE INDEX contract_logs_jsonpathops_idx ON contract_logs USING GIN (value_json jsonb_path_ops)` | ||
); | ||
|
||
logger.info('Running migration 1680181889941_contract_log_json.. creating jsonb_ops index'); | ||
await client.query( | ||
`CREATE INDEX contract_logs_jsonops_idx ON contract_logs USING GIN (value_json jsonb_ops)` | ||
); | ||
|
||
logger.info(`Running migration 1680181889941_contract_log_json.. 100%`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This index is never created, is this an old line?