hana-cli
Version:
HANA Developer Command Line Interface
315 lines (278 loc) • 9.47 kB
JavaScript
// @ts-check
import * as baseLite from '../utils/base-lite.js'
import dbClientClass from "../utils/database/index.js"
import { buildDocEpilogue } from '../utils/doc-linker.js'
export const command = 'tableCopy'
export const aliases = ['tablecopy', 'copyTable', 'copytable']
export const describe = baseLite.bundle.getText("tableCopy")
export const builder = (yargs) => yargs.options(baseLite.getBuilder({
sourceTable: {
alias: ['st'],
type: 'string',
desc: baseLite.bundle.getText("tableCopySourceTable")
},
targetTable: {
alias: ['tt'],
type: 'string',
desc: baseLite.bundle.getText("tableCopyTargetTable")
},
sourceSchema: {
alias: ['ss'],
type: 'string',
default: '**CURRENT_SCHEMA**',
desc: baseLite.bundle.getText("tableCopySourceSchema")
},
targetSchema: {
alias: ['ts'],
type: 'string',
default: '**CURRENT_SCHEMA**',
desc: baseLite.bundle.getText("tableCopyTargetSchema")
},
structureOnly: {
alias: ['so'],
type: 'boolean',
default: false,
desc: baseLite.bundle.getText("tableCopyStructureOnly")
},
dataOnly: {
alias: ['do'],
type: 'boolean',
default: false,
desc: baseLite.bundle.getText("tableCopyDataOnly")
},
where: {
alias: ['w'],
type: 'string',
desc: baseLite.bundle.getText("tableCopyWhere")
},
limit: {
alias: ['l'],
type: 'number',
desc: baseLite.bundle.getText("tableCopyLimit")
},
batchSize: {
alias: ['b', 'batch'],
type: 'number',
default: 1000,
desc: baseLite.bundle.getText("tableCopyBatchSize")
},
dryRun: {
alias: ['dr', 'preview'],
type: 'boolean',
default: false,
desc: baseLite.bundle.getText("dryRun")
},
timeout: {
alias: ['to'],
type: 'number',
default: 3600,
desc: baseLite.bundle.getText("tableCopyTimeout")
},
profile: {
alias: ['p'],
type: 'string',
desc: baseLite.bundle.getText("profile")
}
})).wrap(160).example('hana-cli tableCopy --sourceTable src_table --targetTable tgt_table --batchSize 1000', baseLite.bundle.getText("tableCopyExample")).wrap(160).epilog(buildDocEpilogue('tableCopy', 'mass-operations', ['export', 'import', 'tables']))
export let inputPrompts = {
sourceTable: {
description: baseLite.bundle.getText("tableCopySourceTable"),
type: 'string',
required: true
},
targetTable: {
description: baseLite.bundle.getText("tableCopyTargetTable"),
type: 'string',
required: true
},
sourceSchema: {
description: baseLite.bundle.getText("tableCopySourceSchema"),
type: 'string',
required: false
},
targetSchema: {
description: baseLite.bundle.getText("tableCopyTargetSchema"),
type: 'string',
required: false
},
structureOnly: {
description: baseLite.bundle.getText("tableCopyStructureOnly"),
type: 'boolean',
required: false,
ask: () => false
},
dataOnly: {
description: baseLite.bundle.getText("tableCopyDataOnly"),
type: 'boolean',
required: false,
ask: () => false
},
limit: {
description: baseLite.bundle.getText("tableCopyLimit"),
type: 'number',
required: false,
ask: () => false
},
timeout: {
description: baseLite.bundle.getText("tableCopyTimeout"),
type: 'number',
required: false,
default: 3600,
ask: () => false
},
profile: {
description: baseLite.bundle.getText("profile"),
type: 'string',
required: false,
ask: () => { }
},
dryRun: {
description: baseLite.bundle.getText("dryRun"),
type: 'boolean',
required: false,
ask: () => false
}
}
/**
* Command handler function
* @param {object} argv - Command line arguments from yargs
* @returns {Promise<void>}
*/
export async function handler(argv) {
const base = await import('../utils/base.js')
base.promptHandler(argv, tableCopyMain, inputPrompts)
}
/**
* Copy table structure and/or data
* @param {object} prompts - User prompts with copy options
* @returns {Promise<void>}
*/
export async function tableCopyMain(prompts) {
const base = await import('../utils/base.js')
base.debug('tableCopyMain')
try {
base.setPrompts(prompts)
// Set operation timeout
const timeoutHandle = prompts.timeout > 0
? setTimeout(() => process.exit(1), prompts.timeout * 1000)
: null
// Connect to database
const dbClient = await dbClientClass.getNewClient(prompts)
await dbClient.connect()
// Get current schema if using **CURRENT_SCHEMA**
let sourceSchema = prompts.sourceSchema
let targetSchema = prompts.targetSchema
if (sourceSchema === '**CURRENT_SCHEMA**') {
const result = await dbClient.execSQL("SELECT CURRENT_SCHEMA FROM DUMMY")
sourceSchema = result?.[0]?.CURRENT_SCHEMA || 'PUBLIC'
}
if (targetSchema === '**CURRENT_SCHEMA**') {
const result = await dbClient.execSQL("SELECT CURRENT_SCHEMA FROM DUMMY")
targetSchema = result?.[0]?.CURRENT_SCHEMA || sourceSchema
}
const sourceTable = prompts.sourceTable
const targetTable = prompts.targetTable
console.log(base.bundle.getText("info.startingTableCopy", [
`${sourceSchema}.${sourceTable}`,
`${targetSchema}.${targetTable}`
]))
// Check if source table exists
const tableCheckQuery = `
SELECT TABLE_NAME
FROM SYS.TABLES
WHERE SCHEMA_NAME = ? AND TABLE_NAME = ?
`
const sourceExists = await dbClient.execSQL(tableCheckQuery, [sourceSchema, sourceTable])
if (sourceExists.length === 0) {
throw new Error(base.bundle.getText("error.sourceTableNotFound", [sourceSchema, sourceTable]))
}
let structureCopied = false
let rowsCopied = 0
// Copy structure (unless dataOnly mode)
if (!prompts.dataOnly) {
console.log(base.bundle.getText("info.copyingStructure"))
// Check if target table exists
const targetExists = await dbClient.execSQL(tableCheckQuery, [targetSchema, targetTable])
if (targetExists.length > 0) {
console.log(base.bundle.getText("info.targetTableExists", [targetSchema, targetTable]))
if (prompts.structureOnly) {
throw new Error(base.bundle.getText("error.targetTableAlreadyExists"))
}
} else {
// Create table structure
const createStmt = `CREATE TABLE "${targetSchema}"."${targetTable}" LIKE "${sourceSchema}"."${sourceTable}"`
await dbClient.execSQL(createStmt)
structureCopied = true
console.log(base.bundle.getText("success.structureCopied"))
}
}
// Copy data (unless structureOnly mode)
if (!prompts.structureOnly) {
console.log(base.bundle.getText("info.copyingData"))
// Build SELECT query with optional WHERE and LIMIT
let selectQuery = `SELECT * FROM "${sourceSchema}"."${sourceTable}"`
if (prompts.where) {
selectQuery += ` WHERE ${prompts.where}`
}
if (prompts.limit) {
selectQuery += ` LIMIT ${prompts.limit}`
}
// Get source data
const sourceData = await dbClient.execSQL(selectQuery)
console.log(base.bundle.getText("info.rowsRead", [sourceData.length]))
if (sourceData.length > 0) {
// Get column names
const columnsQuery = `
SELECT COLUMN_NAME
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = ? AND TABLE_NAME = ?
ORDER BY POSITION
`
const columns = await dbClient.execSQL(columnsQuery, [sourceSchema, sourceTable])
const columnNames = columns.map(c => c.COLUMN_NAME)
// Insert data in batches
const batchSize = prompts.batchSize || 1000
const totalRows = sourceData.length
let processedRows = 0
for (let i = 0; i < totalRows; i += batchSize) {
const batch = sourceData.slice(i, Math.min(i + batchSize, totalRows))
// Build INSERT statement
const columnList = columnNames.map(c => `"${c}"`).join(', ')
const valuePlaceholders = columnNames.map(() => '?').join(', ')
const insertStmt = `INSERT INTO "${targetSchema}"."${targetTable}" (${columnList}) VALUES (${valuePlaceholders})`
// Insert batch
for (const row of batch) {
const values = columnNames.map(col => row[col])
await dbClient.execSQL(insertStmt, values)
processedRows++
}
console.log(base.bundle.getText("info.processedRows", [processedRows, totalRows]))
}
rowsCopied = processedRows
console.log(base.bundle.getText("success.dataCopied", [rowsCopied]))
}
}
console.log(base.bundle.getText("success.tableCopyComplete", [
sourceTable,
targetTable,
rowsCopied
]))
if (timeoutHandle) clearTimeout(timeoutHandle)
if (!prompts.quiet) {
const summary = [{
SOURCE: `${sourceSchema}.${sourceTable}`,
TARGET: `${targetSchema}.${targetTable}`,
STRUCTURE_COPIED: structureCopied ? 'YES' : 'NO',
ROWS_COPIED: rowsCopied,
MODE: prompts.structureOnly ? 'STRUCTURE_ONLY' :
prompts.dataOnly ? 'DATA_ONLY' : 'BOTH',
WHERE_FILTER: prompts.where || 'NONE',
LIMIT_APPLIED: prompts.limit || 'NONE'
}]
base.outputTableFancy(summary)
}
await dbClient.disconnect()
} catch (error) {
base.error(base.bundle.getText("error.tableCopy", [error.message]))
}
}