target find
job-minus.js
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
function printHelp() {
console.log(`Usage:
node json-minus.js
Subtracts b.json from a.json by case-insensitive tableName + plainColumn.
Rows from a.json are preserved as-is in out.json when their key does not exist in b.json.
Example:
node json-minus.js a.json b.json out.json
`);
}
function parseArgs(argv) {
if (argv.includes('--help') || argv.includes('-h')) {
printHelp();
process.exit(0);
}
if (argv.length !== 3) {
printHelp();
throw new Error('Exactly three arguments are required: .');
}
return {
aPath: path.resolve(argv[0]),
bPath: path.resolve(argv[1]),
outPath: path.resolve(argv[2])
};
}
function readJsonArray(filePath, label) {
if (!fs.existsSync(filePath)) {
throw new Error(`${label} not found: ${filePath}`);
}
const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!Array.isArray(value)) {
throw new Error(`${label} must be a JSON array: ${filePath}`);
}
return value;
}
function normalizeKey(row) {
return `${String(row && row.tableName ? row.tableName : '').toUpperCase()}|${String(row && row.plainColumn ? row.plainColumn : '').toUpperCase()}`;
}
function hasValidKey(row) {
return Boolean(row && row.tableName && row.plainColumn);
}
function main() {
const args = parseArgs(process.argv.slice(2));
const aRows = readJsonArray(args.aPath, 'a.json');
const bRows = readJsonArray(args.bPath, 'b.json');
const bKeys = new Set(bRows.filter(hasValidKey).map(normalizeKey));
const outRows = aRows.filter((row) => !hasValidKey(row) || !bKeys.has(normalizeKey(row)));
fs.writeFileSync(args.outPath, `${JSON.stringify(outRows, null, 2)}\n`, 'utf8');
console.log(`A rows: ${aRows.length}`);
console.log(`B keys: ${bKeys.size}`);
console.log(`Output rows: ${outRows.length}`);
console.log(`Removed rows: ${aRows.length - outRows.length}`);
console.log(`Output written: ${args.outPath}`);
}
main();
job-minus.js -- end
print_allcol.js start
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const oracledb = require('oracledb');
function parseArgs(argv) {
const args = {
config: path.resolve(__dirname, '..', 'db.ini'),
output: '',
owners: process.env.SCAN_OWNERS || '',
includeSystemOwners: process.env.SCAN_INCLUDE_SYSTEM_OWNERS === 'true'
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
const readValue = () => {
if (i + 1 >= argv.length) {
throw new Error(`${arg} option requires a value.`);
}
i += 1;
return argv[i];
};
if (arg === '--config') args.config = path.resolve(readValue());
else if (arg === '--output') args.output = path.resolve(readValue());
else if (arg === '--owners') args.owners = readValue();
else if (arg === '--include-system-owners') args.includeSystemOwners = true;
else if (arg === '--help' || arg === '-h') {
printHelp();
process.exit(0);
} else {
throw new Error(`Unknown option: ${arg}`);
}
}
return args;
}
function printHelp() {
console.log(`Usage:
node print_allcol.js [options]
Prints every accessible table column in crypto-targets.json object format.
Options:
--config DB ini path. Default: ../db.ini
--output write JSON to file instead of stdout
--owners owner list. Default: all accessible non-system schemas
--include-system-owners include system schemas
`);
}
function readIni(filePath) {
if (!fs.existsSync(filePath)) {
return {};
}
return fs.readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#') && !line.startsWith(';'))
.reduce((acc, line) => {
const index = line.indexOf('=');
if (index > -1) {
acc[line.slice(0, index).trim()] = line.slice(index + 1).trim();
}
return acc;
}, {});
}
function buildDbConfig(args) {
const ini = readIni(args.config);
const user = process.env.DB_USER || process.env.ORACLE_USER || ini.id;
const password = process.env.DB_PASSWORD || process.env.ORACLE_PASSWORD || ini.pass;
const host = process.env.DB_HOST || ini.ip || '127.0.0.1';
const port = process.env.DB_PORT || ini.port || '1521';
const sid = process.env.DB_SID || ini.sid;
const serviceName = process.env.DB_SERVICE_NAME || ini.serviceName || ini.service_name;
const connectString = process.env.DB_CONNECT_STRING
|| ini.connectString
|| ini.connect_string
|| `${host}:${port}/${serviceName || sid}`;
if (!user || !password || !connectString) {
throw new Error('DB connection information is missing. Check db.ini or DB_* environment variables.');
}
return { user, password, connectString };
}
function splitCsv(value) {
return String(value || '')
.split(',')
.map((item) => item.trim().toUpperCase())
.filter(Boolean);
}
function addInListPredicate(columnName, values, bindPrefix, binds) {
const placeholders = values.map((value, index) => {
const key = `${bindPrefix}${index}`;
binds[key] = value;
return `:${key}`;
});
return `${columnName} IN (${placeholders.join(', ')})`;
}
async function getAllColumns(connection, args) {
const owners = splitCsv(args.owners);
const binds = {};
const predicates = [
't.DROPPED = \'NO\'',
't.TEMPORARY = \'N\'',
't.NESTED = \'NO\'',
'c.TABLE_NAME NOT LIKE \'BIN$%\''
];
if (owners.length > 0) {
predicates.push(addInListPredicate('c.OWNER', owners, 'owner', binds));
}
if (!args.includeSystemOwners) {
predicates.push(`c.OWNER NOT IN (
'SYS', 'SYSTEM', 'XDB', 'MDSYS', 'CTXSYS', 'ORDSYS', 'OLAPSYS',
'WMSYS', 'OUTLN', 'DBSNMP', 'APPQOSSYS', 'AUDSYS'
)`);
}
const sql = `
SELECT
c.TABLE_NAME,
c.COLUMN_NAME
FROM ALL_TAB_COLUMNS c
JOIN ALL_TABLES t
ON t.OWNER = c.OWNER
AND t.TABLE_NAME = c.TABLE_NAME
WHERE ${predicates.join('\n AND ')}
ORDER BY c.OWNER, c.TABLE_NAME, c.COLUMN_ID
`;
const result = await connection.execute(sql, binds, { outFormat: oracledb.OUT_FORMAT_OBJECT });
return result.rows.map((row) => ({
tableName: row.TABLE_NAME,
plainColumn: row.COLUMN_NAME,
cryptoType: '',
encColumn: '',
hashColumn: '',
javaProperty: '',
encryptTypeHandler: '',
hashTypeHandler: ''
}));
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const dbConfig = buildDbConfig(args);
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
const connection = await oracledb.getConnection(dbConfig);
try {
const columns = await getAllColumns(connection, args);
const output = `${JSON.stringify(columns, null, 2)}\n`;
if (args.output) {
fs.writeFileSync(args.output, output, 'utf8');
console.error(`Output written: ${args.output}`);
} else {
process.stdout.write(output);
}
} finally {
await connection.close();
}
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
print_allcol.js end
print-empty.js start
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const oracledb = require('oracledb');
function parseArgs(argv) {
const args = {
config: path.resolve(__dirname, '..', 'db.ini'),
output: '',
owners: process.env.SCAN_OWNERS || '',
includeSystemOwners: process.env.SCAN_INCLUDE_SYSTEM_OWNERS === 'true'
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
const readValue = () => {
if (i + 1 >= argv.length) {
throw new Error(`${arg} option requires a value.`);
}
i += 1;
return argv[i];
};
if (arg === '--config') args.config = path.resolve(readValue());
else if (arg === '--output') args.output = path.resolve(readValue());
else if (arg === '--owners') args.owners = readValue();
else if (arg === '--include-system-owners') args.includeSystemOwners = true;
else if (arg === '--help' || arg === '-h') {
printHelp();
process.exit(0);
} else {
throw new Error(`Unknown option: ${arg}`);
}
}
return args;
}
function printHelp() {
console.log(`Usage:
node print-empty.js [options]
Prints columns in crypto-targets.json object format when:
1. the table has zero rows, or
2. the table has rows but the column has no non-null values.
Options:
--config DB ini path. Default: ../db.ini
--output write JSON to file instead of stdout
--owners owner list. Default: all accessible non-system schemas
--include-system-owners include system schemas
`);
}
function readIni(filePath) {
if (!fs.existsSync(filePath)) {
return {};
}
return fs.readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#') && !line.startsWith(';'))
.reduce((acc, line) => {
const index = line.indexOf('=');
if (index > -1) {
acc[line.slice(0, index).trim()] = line.slice(index + 1).trim();
}
return acc;
}, {});
}
function buildDbConfig(args) {
const ini = readIni(args.config);
const user = process.env.DB_USER || process.env.ORACLE_USER || ini.id;
const password = process.env.DB_PASSWORD || process.env.ORACLE_PASSWORD || ini.pass;
const host = process.env.DB_HOST || ini.ip || '127.0.0.1';
const port = process.env.DB_PORT || ini.port || '1521';
const sid = process.env.DB_SID || ini.sid;
const serviceName = process.env.DB_SERVICE_NAME || ini.serviceName || ini.service_name;
const connectString = process.env.DB_CONNECT_STRING
|| ini.connectString
|| ini.connect_string
|| `${host}:${port}/${serviceName || sid}`;
if (!user || !password || !connectString) {
throw new Error('DB connection information is missing. Check db.ini or DB_* environment variables.');
}
return { user, password, connectString };
}
function splitCsv(value) {
return String(value || '')
.split(',')
.map((item) => item.trim().toUpperCase())
.filter(Boolean);
}
function addInListPredicate(columnName, values, bindPrefix, binds) {
const placeholders = values.map((value, index) => {
const key = `${bindPrefix}${index}`;
binds[key] = value;
return `:${key}`;
});
return `${columnName} IN (${placeholders.join(', ')})`;
}
function quoteIdentifier(identifier) {
return `"${String(identifier).replace(/"/g, '""')}"`;
}
function toCryptoTarget(row) {
return {
tableName: row.TABLE_NAME,
plainColumn: row.COLUMN_NAME,
cryptoType: '',
encColumn: '',
hashColumn: '',
javaProperty: '',
encryptTypeHandler: '',
hashTypeHandler: ''
};
}
async function getTables(connection, args) {
const owners = splitCsv(args.owners);
const binds = {};
const predicates = [
'DROPPED = \'NO\'',
'TEMPORARY = \'N\'',
'NESTED = \'NO\'',
'TABLE_NAME NOT LIKE \'BIN$%\''
];
if (owners.length > 0) {
predicates.push(addInListPredicate('OWNER', owners, 'owner', binds));
}
if (!args.includeSystemOwners) {
predicates.push(`OWNER NOT IN (
'SYS', 'SYSTEM', 'XDB', 'MDSYS', 'CTXSYS', 'ORDSYS', 'OLAPSYS',
'WMSYS', 'OUTLN', 'DBSNMP', 'APPQOSSYS', 'AUDSYS'
)`);
}
const sql = `
SELECT OWNER, TABLE_NAME
FROM ALL_TABLES
WHERE ${predicates.join('\n AND ')}
ORDER BY OWNER, TABLE_NAME
`;
const result = await connection.execute(sql, binds, { outFormat: oracledb.OUT_FORMAT_OBJECT });
return result.rows;
}
async function getColumns(connection, owner, tableName) {
const result = await connection.execute(
`
SELECT COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE OWNER = :owner
AND TABLE_NAME = :tableName
ORDER BY COLUMN_ID
`,
{ owner, tableName },
{ outFormat: oracledb.OUT_FORMAT_OBJECT }
);
return result.rows;
}
async function getCount(connection, sql) {
const result = await connection.execute(sql, [], { outFormat: oracledb.OUT_FORMAT_OBJECT });
return result.rows[0].CNT;
}
async function getEmptyColumns(connection, args) {
const tables = await getTables(connection, args);
const emptyColumns = [];
let scannedColumns = 0;
for (const [tableIndex, table] of tables.entries()) {
const tableRef = `${quoteIdentifier(table.OWNER)}.${quoteIdentifier(table.TABLE_NAME)}`;
process.stderr.write(`[${tableIndex + 1}/${tables.length}] ${table.OWNER}.${table.TABLE_NAME}\r`);
const tableRows = await getCount(connection, `SELECT COUNT(*) AS CNT FROM ${tableRef}`);
const columns = await getColumns(connection, table.OWNER, table.TABLE_NAME);
scannedColumns += columns.length;
for (const column of columns) {
if (tableRows === 0) {
emptyColumns.push(toCryptoTarget({
TABLE_NAME: table.TABLE_NAME,
COLUMN_NAME: column.COLUMN_NAME
}));
continue;
}
const columnRows = await getCount(
connection,
`SELECT COUNT(*) AS CNT FROM ${tableRef} WHERE ${quoteIdentifier(column.COLUMN_NAME)} IS NOT NULL`
);
if (columnRows === 0) {
emptyColumns.push(toCryptoTarget({
TABLE_NAME: table.TABLE_NAME,
COLUMN_NAME: column.COLUMN_NAME
}));
}
}
}
process.stderr.write(' '.repeat(120) + '\r');
console.error(`Tables scanned: ${tables.length}`);
console.error(`Columns scanned: ${scannedColumns}`);
console.error(`Empty columns: ${emptyColumns.length}`);
return emptyColumns;
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const dbConfig = buildDbConfig(args);
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
const connection = await oracledb.getConnection(dbConfig);
try {
const columns = await getEmptyColumns(connection, args);
const output = `${JSON.stringify(columns, null, 2)}\n`;
if (args.output) {
fs.writeFileSync(args.output, output, 'utf8');
console.error(`Output written: ${args.output}`);
} else {
process.stdout.write(output);
}
} finally {
await connection.close();
}
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
print-empty.js end
scan-card-col.js start
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const oracledb = require('oracledb');
const STRING_TYPES = new Set([
'CHAR',
'VARCHAR2',
'NCHAR',
'NVARCHAR2',
'CLOB',
'NCLOB'
]);
const CARD_COLUMN_REGEX = /^(?:card_?no|card_?num|card_?number|credit_?card|credit_?card_?no|credit_?card_?number|cc_?no|cc_?num)$/i;
function parseArgs(argv) {
const args = {
config: path.resolve(__dirname, '..', 'db.ini'),
targets: path.resolve(__dirname, 'crypto-targets.json'),
output: path.resolve(__dirname, 'findcardoutput.txt'),
owners: process.env.SCAN_OWNERS || '',
sampleLimit: Number(process.env.SCAN_SAMPLE_LIMIT || 5),
maxLength: Number(process.env.SCAN_MAX_LENGTH || 4000),
includeSystemOwners: process.env.SCAN_INCLUDE_SYSTEM_OWNERS === 'true'
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
const readValue = () => {
if (i + 1 >= argv.length) {
throw new Error(`${arg} option requires a value.`);
}
i += 1;
return argv[i];
};
if (arg === '--config') args.config = path.resolve(readValue());
else if (arg === '--targets') args.targets = path.resolve(readValue());
else if (arg === '--output') args.output = path.resolve(readValue());
else if (arg === '--owners') args.owners = readValue();
else if (arg === '--sample-limit') args.sampleLimit = Number(readValue());
else if (arg === '--max-length') args.maxLength = Number(readValue());
else if (arg === '--include-system-owners') args.includeSystemOwners = true;
else if (arg === '--help' || arg === '-h') {
printHelp();
process.exit(0);
} else {
throw new Error(`Unknown option: ${arg}`);
}
}
if (!Number.isInteger(args.sampleLimit) || args.sampleLimit < 1 || args.sampleLimit > 100) {
throw new Error('--sample-limit must be an integer from 1 to 100.');
}
if (!Number.isInteger(args.maxLength) || args.maxLength < 1 || args.maxLength > 4000) {
throw new Error('--max-length must be an integer from 1 to 4000.');
}
return args;
}
function printHelp() {
console.log(`Usage:
node scan-card-col.js [options]
Finds table columns that are not present in crypto-targets.json and look like
credit card candidates by sample value or column name.
Options:
--config DB ini path. Default: ../db.ini
--targets crypto-targets.json path. Default: ./crypto-targets.json
--output output file path. Default: ./findcardoutput.txt
--owners owner list. Default: all accessible non-system schemas
--sample-limit sample count per column. Default: 5
--max-length max value length for scan. Default: 4000
--include-system-owners include system schemas
`);
}
function readIni(filePath) {
if (!fs.existsSync(filePath)) {
return {};
}
return fs.readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#') && !line.startsWith(';'))
.reduce((acc, line) => {
const index = line.indexOf('=');
if (index > -1) {
acc[line.slice(0, index).trim()] = line.slice(index + 1).trim();
}
return acc;
}, {});
}
function buildDbConfig(args) {
const ini = readIni(args.config);
const user = process.env.DB_USER || process.env.ORACLE_USER || ini.id;
const password = process.env.DB_PASSWORD || process.env.ORACLE_PASSWORD || ini.pass;
const host = process.env.DB_HOST || ini.ip || '127.0.0.1';
const port = process.env.DB_PORT || ini.port || '1521';
const sid = process.env.DB_SID || ini.sid;
const serviceName = process.env.DB_SERVICE_NAME || ini.serviceName || ini.service_name;
const connectString = process.env.DB_CONNECT_STRING
|| ini.connectString
|| ini.connect_string
|| `${host}:${port}/${serviceName || sid}`;
if (!user || !password || !connectString) {
throw new Error('DB connection information is missing. Check db.ini or DB_* environment variables.');
}
return { user, password, connectString };
}
function splitCsv(value) {
return String(value || '')
.split(',')
.map((item) => item.trim().toUpperCase())
.filter(Boolean);
}
function addInListPredicate(columnName, values, bindPrefix, binds) {
const placeholders = values.map((value, index) => {
const key = `${bindPrefix}${index}`;
binds[key] = value;
return `:${key}`;
});
return `${columnName} IN (${placeholders.join(', ')})`;
}
function normalizeKey(tableName, columnName) {
return `${String(tableName || '').toUpperCase()}.${String(columnName || '').toUpperCase()}`;
}
function loadExistingTargets(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`crypto-targets.json not found: ${filePath}`);
}
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!Array.isArray(parsed)) {
throw new Error('crypto-targets.json must be a JSON array.');
}
const keys = new Set();
for (const item of parsed) {
if (item && item.tableName && item.plainColumn) {
keys.add(normalizeKey(item.tableName, item.plainColumn));
}
}
return keys;
}
function quoteIdentifier(identifier) {
return `"${String(identifier).replace(/"/g, '""')}"`;
}
function isLobType(dataType) {
return dataType === 'CLOB' || dataType === 'NCLOB';
}
function sampleExpression(column, dataType, maxLength) {
const quotedColumn = quoteIdentifier(column);
if (isLobType(dataType)) {
return `DBMS_LOB.SUBSTR(${quotedColumn}, ${maxLength}, 1)`;
}
return `SUBSTR(${quotedColumn}, 1, ${maxLength})`;
}
async function getCandidateColumns(connection, args, existingTargets) {
const owners = splitCsv(args.owners);
const binds = {};
const stringTypePredicate = addInListPredicate('c.DATA_TYPE', Array.from(STRING_TYPES), 'type', binds);
const predicates = [
`(${stringTypePredicate} OR REGEXP_LIKE(c.COLUMN_NAME, '^(CARD_?NO|CARD_?NUM|CARD_?NUMBER|CREDIT_?CARD|CREDIT_?CARD_?NO|CREDIT_?CARD_?NUMBER|CC_?NO|CC_?NUM)$', 'i'))`,
't.DROPPED = \'NO\'',
't.TEMPORARY = \'N\'',
't.NESTED = \'NO\''
];
if (owners.length > 0) {
predicates.push(addInListPredicate('c.OWNER', owners, 'owner', binds));
}
if (!args.includeSystemOwners) {
predicates.push(`c.OWNER NOT IN (
'SYS', 'SYSTEM', 'XDB', 'MDSYS', 'CTXSYS', 'ORDSYS', 'OLAPSYS',
'WMSYS', 'OUTLN', 'DBSNMP', 'APPQOSSYS', 'AUDSYS'
)`);
}
const sql = `
SELECT
c.OWNER,
c.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE
FROM ALL_TAB_COLUMNS c
JOIN ALL_TABLES t
ON t.OWNER = c.OWNER
AND t.TABLE_NAME = c.TABLE_NAME
WHERE ${predicates.join('\n AND ')}
ORDER BY c.OWNER, c.TABLE_NAME, c.COLUMN_ID
`;
const result = await connection.execute(sql, binds, { outFormat: oracledb.OUT_FORMAT_OBJECT });
return result.rows.filter((column) => !existingTargets.has(normalizeKey(column.TABLE_NAME, column.COLUMN_NAME)));
}
async function getSamples(connection, column, args) {
if (!STRING_TYPES.has(column.DATA_TYPE)) {
return [];
}
const sql = `
SELECT SAMPLE_VALUE
FROM (
SELECT ${sampleExpression(column.COLUMN_NAME, column.DATA_TYPE, args.maxLength)} AS SAMPLE_VALUE
FROM ${quoteIdentifier(column.OWNER)}.${quoteIdentifier(column.TABLE_NAME)}
WHERE ${quoteIdentifier(column.COLUMN_NAME)} IS NOT NULL
AND ROWNUM <= :sampleLimit
)
WHERE SAMPLE_VALUE IS NOT NULL
`;
const result = await connection.execute(
sql,
{ sampleLimit: args.sampleLimit },
{ outFormat: oracledb.OUT_FORMAT_OBJECT }
);
return result.rows.map((row) => String(row.SAMPLE_VALUE));
}
function luhnCheck(digits) {
let sum = 0;
let shouldDouble = false;
for (let i = digits.length - 1; i >= 0; i -= 1) {
let digit = Number(digits[i]);
if (shouldDouble) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
shouldDouble = !shouldDouble;
}
return sum % 10 === 0;
}
function findCardNumbers(value) {
const candidates = String(value).match(/\b(?:\d[ -]?){13,19}\b/g) || [];
return candidates
.map((candidate) => candidate.replace(/[ -]/g, ''))
.filter((digits) => digits.length >= 13 && digits.length <= 19 && luhnCheck(digits));
}
function classifyColumn(column, samples) {
const reasons = [];
const matchedSamples = [];
if (CARD_COLUMN_REGEX.test(column.COLUMN_NAME)) {
reasons.push('COLUMN_NAME_CARD');
}
for (const sample of samples) {
const cards = findCardNumbers(sample);
if (cards.length > 0) {
reasons.push('CARD_VALUE');
matchedSamples.push({
type: 'CARD_VALUE',
value: sample,
normalizedCards: cards
});
}
}
return {
reasons: Array.from(new Set(reasons)),
matchedSamples
};
}
function maskLineBreaks(value) {
return String(value).replace(/\r/g, '\\r').replace(/\n/g, '\\n');
}
function maskCardNumber(digits) {
if (digits.length <= 8) {
return '*'.repeat(digits.length);
}
return `${digits.slice(0, 6)}${'*'.repeat(digits.length - 10)}${digits.slice(-4)}`;
}
function buildOutput({ args, existingTargetCount, scannedColumns, matches, errors }) {
const lines = [];
lines.push('Credit Card Column Candidates');
lines.push(`GeneratedAt=${new Date().toISOString()}`);
lines.push(`TargetsFile=${args.targets}`);
lines.push(`ExistingTargets=${existingTargetCount}`);
lines.push(`ScannedColumns=${scannedColumns}`);
lines.push(`MatchedColumns=${matches.length}`);
lines.push(`ErrorColumns=${errors.length}`);
lines.push('');
for (const match of matches) {
lines.push(`[MATCH] ${match.owner}.${match.table}.${match.column} (${match.dataType})`);
lines.push(`Reasons=${match.reasons.join(', ')}`);
if (match.matchedSamples.length > 0) {
lines.push('Samples=');
for (const sample of match.matchedSamples) {
const cards = sample.normalizedCards.map(maskCardNumber).join(', ');
lines.push(` - ${sample.type}: ${maskLineBreaks(sample.value)} | cards=${cards}`);
}
}
lines.push('');
}
if (errors.length > 0) {
lines.push('[ERRORS]');
for (const error of errors) {
lines.push(`${error.owner}.${error.table}.${error.column}: ${error.error}`);
}
lines.push('');
}
return lines.join('\n');
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const existingTargets = loadExistingTargets(args.targets);
const dbConfig = buildDbConfig(args);
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
const connection = await oracledb.getConnection(dbConfig);
try {
const columns = await getCandidateColumns(connection, args, existingTargets);
const matches = [];
const errors = [];
for (const [index, column] of columns.entries()) {
const label = `${column.OWNER}.${column.TABLE_NAME}.${column.COLUMN_NAME}`;
process.stderr.write(`[${index + 1}/${columns.length}] ${label}\r`);
try {
const samples = await getSamples(connection, column, args);
const classified = classifyColumn(column, samples);
if (classified.reasons.length > 0) {
matches.push({
owner: column.OWNER,
table: column.TABLE_NAME,
column: column.COLUMN_NAME,
dataType: column.DATA_TYPE,
reasons: classified.reasons,
matchedSamples: classified.matchedSamples
});
}
} catch (error) {
errors.push({
owner: column.OWNER,
table: column.TABLE_NAME,
column: column.COLUMN_NAME,
dataType: column.DATA_TYPE,
error: error.message
});
}
}
process.stderr.write(' '.repeat(120) + '\r');
const output = buildOutput({
args,
existingTargetCount: existingTargets.size,
scannedColumns: columns.length,
matches,
errors
});
fs.writeFileSync(args.output, output, 'utf8');
console.log(`Output written: ${args.output}`);
console.log(`Scanned columns: ${columns.length}`);
console.log(`Matched columns: ${matches.length}`);
console.log(`Error columns: ${errors.length}`);
} finally {
await connection.close();
}
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
scan-card-col.js end
댓글
댓글 쓰기