diff --git a/docs/pages/programming/sql.md b/docs/pages/programming/sql.md index e9d5468052..d6a506f834 100644 --- a/docs/pages/programming/sql.md +++ b/docs/pages/programming/sql.md @@ -552,6 +552,15 @@ system table. The ```REPLACE TABLE``` statement can be used to replace a table with another table. +The source table is specified using [foreign table](#foreign-tables) syntax. For a database running +on the same machine, use ```LOCAL_database.tablename```: + +```sql +REPLACE TABLE mytable WITH LOCAL_sourcedb.sourcetable; +``` + +For remote databases, use ```database.tablename``` (requires [comdb2db](clients.html#comdb2db) configuration). + Below is a list of requirements that must be satisfied to use this statement: - The source and destination databases have the same datastripe and blobstripe settings. - There is no network firewall preventing the destination database from communicating diff --git a/schemachange/sc_import.c b/schemachange/sc_import.c index 6c8fc89891..cd5d752048 100644 --- a/schemachange/sc_import.c +++ b/schemachange/sc_import.c @@ -1812,6 +1812,14 @@ static enum comdb2_import_op get_import_rcode_from_tmpdb_rcode(const int rc) { int do_import(struct ireq *iq, struct schema_change_type *sc, tran_type *tran) { + extern int gbl_is_physical_replicant; + if (gbl_is_physical_replicant) { + logmsg(LOGMSG_ERROR, "%s: bulk import into a physical replicant is not allowed\n", __func__); + errstat_set_rcstrf(&iq->errstat, COMDB2_IMPORT_RC_INTERNAL, + "bulk import into a physical replicant is not allowed"); + return COMDB2_IMPORT_RC_INTERNAL; + } + const char * const src_tablename = sc->import_src_tablename; const char * const srcdb = sc->import_src_dbname; const char * const dst_tablename = sc->tablename; diff --git a/sqlite/src/comdb2build.c b/sqlite/src/comdb2build.c index c4d9c6c205..513915266e 100644 --- a/sqlite/src/comdb2build.c +++ b/sqlite/src/comdb2build.c @@ -1771,6 +1771,11 @@ void comdb2bulkimport(Parse* pParse, Token* nm,Token* lnm, Token* nm2, Token* ln void comdb2Replace(Parse* pParse, Token *nm, Token *nm2, Token *nm3) { + if (gbl_is_physical_replicant) { + setError(pParse, SQLITE_MISUSE, "bulk import into a physical replicant is not allowed"); + return; + } + if (gbl_disable_sql_table_replacement) { setError(pParse, SQLITE_MISUSE, "sql table replacement is disabled"); return; diff --git a/tests/phys_rep.test/bulkimport.sh b/tests/phys_rep.test/bulkimport.sh new file mode 100755 index 0000000000..e8f9489d1a --- /dev/null +++ b/tests/phys_rep.test/bulkimport.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +bash -n "$0" || exit 1 +source ${TESTSROOTDIR}/tools/runit_common.sh +set -o pipefail +set -x + +DBNAME=$1 +DBDIR=$2 +destdb=${TESTCASE}dest${TESTID} #from runit +tmpdb_name="tmp${DBNAME}" +tmpdb_dir="${DBDIR}/${tmpdb_name}" + +function check_rc() { + local cmd="$1" + eval $cmd + if [[ $? -ne 0 ]]; then + echo "failed running $cmd" + cleanup + exit 1 + fi +} + +# assumes we have comdb2db.cfg on testdir +# cause we sourcing runit_common.sh +function cp_comdb2dbcfg() { + dir=$1 + name=$2 + echo "comdb2_config:default_type=local" > "$dir/comdb2db.cfg" + echo "comdb2_config:ssl_cert_path=$TESTDIR" >>"$dir/comdb2db.cfg" + echo "comdb2_config:allow_pmux_route:true" >> "$dir/comdb2db.cfg" +} + + +function cleanup() { + # cleanup tmpdb only - parent and physrep are managed by runit + local name="$tmpdb_name" dir="$tmpdb_dir" + if [[ -f "$dir/${name}.pid" ]]; then + kill -9 $(cat "$dir/${name}.pid") 2>/dev/null || true + fi +} + +function create_db() { + local name=$1 dir=$2 + mkdir -p "$dir" + check_rc "$COMDB2_EXE --create $name -dir $dir" + $COMDB2_EXE $name --lrl $dir/${name}.lrl --pidfile $dir/${name}.pid >"$DBDIR/${name}.log" 2>&1 & + cp_comdb2dbcfg "$dir" "$name" + # Also add tmpdb to parent's config so destdb can find it + echo "$name $dir $dir/${name}.lrl" >> "$DBDIR/comdb2db.cfg" + sleep 5 + check_rc "${CDB2SQL_EXE} --cdb2cfg $dir/comdb2db.cfg $name default 'select 1' " + check_rc "${CDB2SQL_EXE} --cdb2cfg $dir/comdb2db.cfg $name -f ${TESTDIR}/${TESTCASE}.test/1-create-table.src.sql default " + check_rc "${CDB2SQL_EXE} --cdb2cfg $dir/comdb2db.cfg $name default \"insert into t1 (id) values (1)\" " +} + +function test_bulkimport_into_physrep() { + # Given: destdb is already a physrep (set up by runit) + + # When: bulk import into the physrep (destdb) + local tbl="t1" + local output + output=$(${CDB2SQL_EXE} --cdb2cfg $DBDIR/comdb2db.cfg $destdb --host localhost \ + "replace table $tbl with LOCAL_$tmpdb_name.$tbl" 2>&1) + local rc=$? + + # Then: bulkimport should fail with the correct error message + if [[ $rc -eq 0 ]] || ! echo "$output" | grep -q "physical replicant" ; then + echo "Fail, rc=$rc, output: $output" + cleanup + exit 1 + fi + + echo "SUCCESS: bulkimport rejected with error: $output" +} + +function test_bulkimport_from_physrep() { + # Given: destdb is a physrep with replicated data from DBNAME + # We reuse tmpdb as the destination and import FROM the physrep + local tbl="t1" + + check_rc "${CDB2SQL_EXE} ${CDB2_OPTIONS} $DBNAME default \"drop table if exists $tbl\" " + check_rc "${CDB2SQL_EXE} ${CDB2_OPTIONS} $DBNAME default -f ${TESTDIR}/${TESTCASE}.test/1-create-table.src.sql" + check_rc "${CDB2SQL_EXE} ${CDB2_OPTIONS} $DBNAME default \"insert into $tbl (id) values (100), (200), (300)\" " + sleep 5 + check_rc "${CDB2SQL_EXE} --cdb2cfg $tmpdb_dir/comdb2db.cfg $tmpdb_name default \"delete from $tbl where 1\" " + + # When: bulk import FROM the physrep to the tmpdb + local output + output=$(${CDB2SQL_EXE} --cdb2cfg $tmpdb_dir/comdb2db.cfg $tmpdb_name default \ + "replace table $tbl with LOCAL_$destdb.$tbl" 2>&1) + local rc=$? + + # Then: bulkimport should succeed + if [[ $rc -ne 0 ]]; then + echo "FAIL: bulkimport from physrep failed with rc=$rc" + echo "Output: $output" + cleanup + exit 1 + fi + + # Verify data was imported + local cnt + cnt=$(${CDB2SQL_EXE} --tabs --cdb2cfg $tmpdb_dir/comdb2db.cfg $tmpdb_name default "select count(*) from $tbl") + if [[ $cnt -eq 0 ]]; then + echo "FAIL: no records found in $tmpdb_name.$tbl after import from physrep" + cleanup + exit 1 + fi + + echo "SUCCESS: bulk import from physrep succeeded, imported $cnt records" +} + +function main() { + create_db "$tmpdb_name" "$tmpdb_dir" + + test_bulkimport_into_physrep + + test_bulkimport_from_physrep + + echo "All tests passed" + cleanup +} + +main diff --git a/tests/phys_rep.test/runit b/tests/phys_rep.test/runit index 2dff3919d2..f0607a6033 100755 --- a/tests/phys_rep.test/runit +++ b/tests/phys_rep.test/runit @@ -211,8 +211,17 @@ trap - INT EXIT override_physrep_sp setup_replicant + +${CDB2SQL_EXE} ${CDB2_OPTIONS} $dbname default "exec procedure sys.cmd.send('semver 8.1.0')" +${CDB2SQL_EXE} --host localhost $destdb default "exec procedure sys.cmd.send('semver 8.1.0')" + ./generate_tests.sh run_tests + +# (physrep needs to be alive) +./bulkimport.sh $dbname ${DBDIR} +[[ $? -ne 0 ]] && failexit "bulkimport test failed" + cleanup exit 0