remove trailing comma in mysql2sqlite script using awk gsub - mysql

I found a script to turn a mysql dump into an sqlite insertable code,
and i am trying to get rid of trailing comma's before parentheses. (so the comma after 'DEFAULT NULL'. I use awk and gsub here and there. As you could guess I am not very familiar with regex.
link to script https://gist.github.com/esperlu/943776
CREATE TABLE "table_name" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" varchar(100) NOT NULL,
"created_at" datetime NOT NULL,
"deleted_at" datetime DEFAULT NULL,
);
At this point I use the following line in the script:
#Replace trailing commas
/\,\n\)/ { /\,\n\)/, "\, " }
But this gives me the following error in bash
cmd. line:52: /\,\n\)/ { /\,\n\)/, "\, " }
awk: cmd. line:52: ^ syntax error
awk: cmd. line:52: warning: escape sequence `\,' treated as plain `,'
/\,\n\)/ { /\,\n\)/, "\, " }
awk: cmd. line:52: ^ syntax error
awk: cmd. line:52: warning: escape sequence `\,' treated as plain `,'
sql exported to stub-testdb.sqlite
here is my full script into which I added the lines to remove trailing commas
#!/bin/sh
# Converts a mysqldump file into a Sqlite 3 compatible file. It also extracts the MySQL `KEY xxxxx` from the
# CREATE block and create them in separate commands _after_ all the INSERTs.
# Awk is choosen because it's fast and portable. You can use gawk, original awk or even the lightning fast mawk.
# The mysqldump file is traversed only once.
# Usage: $ ./mysql2sqlite mysqldump-opts db-name | sqlite3 database.sqlite
# Example: $ ./mysql2sqlite --no-data -u root -pMySecretPassWord myDbase | sqlite3 database.sqlite
# Thanks to and #artemyk and #gkuenning for their nice tweaks.
mysqldump --compatible=ansi --skip-extended-insert --compact "$#" | \
awk '
# Replace PRIMARY KEY if there is AUTO_INCREMENT
BEGIN { RS="/CREATE TABLE /" }
/NOT NULL AUTO_INCREMENT/ {
gsub( /\n PRIMARY KEY \(\"[a-z_]+\"\)/, "" )
print
next
}
' | \
awk '
BEGIN {
FS=",$"
print "PRAGMA synchronous = OFF;"
print "PRAGMA journal_mode = MEMORY;"
print "BEGIN TRANSACTION;"
}
# CREATE TRIGGER statements have funny commenting. Remember we are in trigger.
/^\/\*.*CREATE.*TRIGGER/ {
gsub( /^.*TRIGGER/, "CREATE TRIGGER" )
print
inTrigger = 1
next
}
# The end of CREATE TRIGGER has a stray comment terminator
/END \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next }
# The r est of triggers just get passed through
inTrigger != 0 { print; next }
# Skip other comments
/^\/\*/ { next }
# Print all `INSERT` lines. The single quotes are protected by another single quote.
/INSERT/ {
gsub( /\\\047/, "\047\047" )
gsub(/\\n/, "\n")
gsub(/\\r/, "\r")
gsub(/\\"/, "\"")
gsub(/\\\\/, "\\")
gsub(/\\\032/, "\032")
print
next
}
# Print the `CREATE` line as is and capture the table name.
/^CREATE/ {
print
if ( match( $0, /\"[^\"]+/ ) ) tableName = substr( $0, RSTART+1, RLENGTH-1 )
}
#Replace AUTO_INCREMENT with AUTOINCREMENT
/int\([0-9]+\) NOT NULL AUTO_INCREMENT/ { gsub( /int\([0-9]+\) NOT NULL AUTO_INCREMENT/, "INTEGER PRIMARY KEY AUTOINCREMENT" ) }
#Replace table and column COMMENT
/ COMMENT '.*'/ { gsub( / COMMENT '.*'/, "" ) }
#Replace trailing commas
/\,\n\)/ { /\,\n\)/, "\, " }
# Replace `FULLTEXT KEY` or any other `XXXXX KEY` except PRIMARY by `KEY`
/^ [^"]+KEY/ && !/^ PRIMARY KEY/ { gsub( /.+KEY/, " KEY" ) }
# Get rid of field lengths in KEY lines
/ KEY/ { gsub(/\([0-9]+\)/, "") }
# Print all fields definition lines except the `KEY` lines.
/^ / && !/^( KEY|\);)/ {
gsub( /AUTO_INCREMENT/, "" )
gsub( /(CHARACTER SET|character set) [^ ]+ /, "" )
gsub( /DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP|default current_timestamp on update current_timestamp/, "" )
gsub( /(COLLATE|collate) [^ ]+ /, "" )
gsub(/(ENUM|enum)[^)]+\)/, "text ")
gsub(/(SET|set)\([^)]+\)/, "text ")
gsub(/UNSIGNED|unsigned/, "")
if (prev) print prev ","
prev = $1
}
# `KEY` lines are extracted from the `CREATE` block and stored in array for later print
# in a separate `CREATE KEY` command. The index name is prefixed by the table name to
# avoid a sqlite error for duplicate index name.
/^( KEY|\);)/ {
if (prev) print prev
prev=""
if ($0 == ");"){
print
} else {
if ( match( $0, /\"[^"]+/ ) ) indexName = substr( $0, RSTART+1, RLENGTH-1 )
if ( match( $0, /\([^()]+/ ) ) indexKey = substr( $0, RSTART+1, RLENGTH-1 )
key[tableName]=key[tableName] "CREATE INDEX \"" tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n"
}
}
# Print all `KEY` creation lines.
END {
for (table in key) printf key[table]
print "END TRANSACTION;"
}
'
exit 0

Using GNU awk for multi-char RS and gensub():
$ gawk -v RS='^$' -v ORS= '{$0=gensub(/,(\s*\))/,"\\1","g")}1' file
CREATE TABLE "table_name" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" varchar(100) NOT NULL,
"created_at" datetime NOT NULL,
"deleted_at" datetime DEFAULT NULL
);

Related

Powershell: Unable to insert into MySQL date, int values with null

I have a MySQL table table_foo with columns col1 of type DATETIME ,col2 of type int, both accepts NULL values but when inserted from Powershell throws error.
$oMYSQLCommand.CommandText='INSERT into `table_foo` (`col1`,`col2`) VALUES("' + $null + '", "' + $null + '")'
$iRowsAffected=$oMYSQLCommand.ExecuteNonQuery()
also tried using [DBNull]::Value as ref in
$oMYSQLCommand.CommandText='INSERT into `table_foo` (`col1`,`col2`) VALUES("' + [DBNull]::Value + '", "' + $null + '")'
Error
Error: Exception calling "ExecuteNonQuery" with "0" argument(s): "Incorrect datetime value: '' for column 'col1' at row 1"
Since you're constructing a string into which the null values are to be embedded, you must represent the null values using SQL syntax, which means: verbatim NULL (without surrounding single quotes):
$oMYSQLCommand.CommandText =
'INSERT into `table_foo` (`col1`,`col2`) VALUES (NULL, NULL)'
If the values come from PowerShell variables that are situationally $null and must therefore conditionally be translated to verbatim NULL, the best approach is to use a helper function, as demonstrated in this answer.
In the simplest case, with all variables containing strings, if you want to treat an empty string or a $null value as a SQL NULL,
you could define a function tf (short for: transform) as follows:
function tf ($val) {
if ([string]::IsNullOrEmpty($val)) { 'NULL' } else { "'{0}'" -f $val }
}
You could then use the function as follows, with $var1 and $var2 containing the values to embed:
$oMYSQLCommand.CommandText =
'INSERT into `table_foo` (`col1`,`col2`) VALUES ({0}, {1})' -f
(tf $var1), (tf $var2)
One more simple way of #mklement0's answer is. So the issue here is MySQL accepts variable values with quotes and NULL without quotes. If NULL in enclosed in quotes it is treated as string value. So while using variable concatenate quotes " if the variable value is not NULL
if($var_x -eq ""){$var_x ="NULL"}else{'var_x = '"'+ $var_x +'"'}
if($var_y -eq ""){$var_y ="NULL"}else{'var_y = '"'+ $var_y +'"'}
$oMYSQLCommand.CommandText='INSERT into `table_foo` (`col1`,`col2`) VALUES(' + $var_x + ',' + $var_y + ')'
$iRowsAffected=$oMYSQLCommand.ExecuteNonQuery()
try typing NULL, dont use variable:
VALUES('NULL','NULL')
something like this:
$oMYSQLCommand.CommandText="INSERT into `table_foo' (`col1`,`col2`) VALUES('NULL','NULL')"

XQuery to generate DROP TABLE IF EXISTS + INSERT INTO statements

I'm using XQuery to go over a bunch of XML files and extract indexed terms and turn them into SQL insert statements. This is very straightforward:
xquery version "3.0";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "text";
for $index in collection(/db/letters/)//index/text()
return
concat("INSERT INTO `indices` SET index='", $index, "';")
This generates statements like:
INSERT INTO `indices` SET index='foo';
INSERT INTO `indices` SET index='bar';
which is all fine and dandy. But I would like to output some text once before and once after all those statements, namely, first:
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for `indices`
-- ----------------------------
DROP TABLE IF EXISTS `indices`;
CREATE TABLE `indices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`norm` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;
and, at the end,
SET FOREIGN_KEY_CHECKS = 1;"
In PHP this would be a non-brainer, but in XQuery it's much more difficult, especially for non-advanced users like myself.
The FLOWR expressions seem very easy and logical when outputting XML, but I can't figure out how to concat the cumulative return with two other strings.
Any pointers will be warmly appreciated.
I use XQuery to do this frequently. It is sometimes helpful to remember that XQuery is really all about sequences and that FLOWR expressions are just one way to generate a sequence. There are several ways to achieve what you are attempting. Examples follow.
xquery version "3.0";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "text";
let $newline := '
'
let $beginning :=
( "your first line",
"your second line",
"your third line",
...)
let $middle :=
for $index in collection(/db/letters/)//index/text()
return concat("INSERT INTO `indices` SET index='", $index, "';")
let $end := ( "your first end line", "your second end line", ... )
return
string-join( ($beginning, $middle, $end), $newline )
or similar:
let $newline := '
'
let $lines :=
(
( "your first line",
"your second line",
"your third line",
...),
(for $index in collection(/db/letters/)//index/text()
return concat("INSERT INTO `indices` SET index='", $index, "';")
),
( "your first end line", "your second end line", ... )
)
return string-join( $lines, $newline )
or some processors will let you create text nodes using syntax such that your query can produce a sequence of text nodes with a newline at the end of each. Though, please note the sequence inside the text { } element may be outputted separated by tabs so in the example below there may be a tab before the newline. That can be overcome by concat()'ing everything in the text { } node.
let $newline := '
'
return
( text{ 'your first line', $newline },
text{ 'your second line', $newline },
text{ '...', $newline },
(for $index in collection(/db/letters/)//index/text()
return text{concat("INSERT INTO `indices` SET index='", $index, "';"),$newline}
),
text {"your first end line", $newline },
text {"your second end line", $newline },
...
)

Sqlite3 - How to import NULL values from csv

I have dumped a mysql table as CSV. In this CSV file, the NULL values are written as \N
Now I want to import this data into sqlite database, but I am unable to tell sqlite that \N are null values. It is treating it as a string and that column value is stored as "\N" instead of NULL.
Can anyone guide in how to use .nullvalue dot command from sqlite. I am unable to set \N as nullvalue.
sqlite> .show
nullvalue: ""
sqlite> .nullvalue \N
sqlite> .show
nullvalue: "N"
sqlite> .nullvalue '\N'
sqlite> .show
nullvalue: "\\N"
sqlite> .nullvalue "\N"
sqlite> .show
nullvalue: "N"
sqlite> .nullvalue \\N
sqlite> .show
nullvalue: "\\N"
sqlite> .nullvalue '\'N
Usage: .nullvalue STRING
sqlite> .nullvalue '\\'N
Usage: .nullvalue STRING
sqlite> .nullvalue \\N
sqlite> .show
nullvalue: "\\N"
sqlite>
This is the output after every value of nullvalue
sqlite> .import /tmp/mysqlDump.csv employee
sqlite> select count(*) from employee where updatedon='\N';
94143
sqlite> select count(*) from employee where updatedon is null;
0
How can I tell sqlite to treat \N as NULL value? I cannot use empty string as NULL value as my data contains empty strings.
CSV files contain only text values. It is not possible to import NULL values from a CSV file.
To convert the \N values into NULLs, just use UPDATE afterwards:
UPDATE employee SET updatedon = NULL WHERE updatedon = '\N';
When sqlite is configured to check foreign key references, a method that relies on doing an update after the import is not possible, because the import would fail (the foreign key constraint would fail) and there would be no row to update.
For that, and the cases where doing an update after the import is unacceptable, you must modify the shell.c file in the amalgamation (as shown below), and compile a new sqlite(.exe) binary.
The change to be made is to bind the parameter for the field to NULL when the field is empty (with sqlite3_bind_null) instead of unconditionally binding it as a text field with sqlite3_bind_text as is currently done.
An example of making that change to sqlite version v 3.33.0 2020-08-14 is below (as a patch diff).
The example has the changes behind a new compile-time option, SQLITE_IMPORT_NULL_IF_EMPTY, so to enable it you'd need to define it when compiling like so:
cc -DSQLITE_IMPORT_NULL_IF_EMPTY <other options> shell.c sqlite3.c -o sqlite3
Example full compilation command with recommended options (and a few others set):
cc -Os -DSQLITE_IMPORT_NULL_IF_EMPTY -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_USE_ALLOCA -DSQLITE_OMIT_AUTOINIT -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_ENABLE_NULL_TRIM -DSQLITE_ENABLE_RBU -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DHAVE_USLEEP -DHAVE_READLINE shell.c sqlite3.c -lreadline -lncurses -o sqlite3
Patch file contents:
--- sqlite-amalgamation-3330000/shell.c 2020-08-14 13:42:48.000000000 +0000
+++ shell.c 2020-10-07 13:23:39.000000000 +0000
## -17845,7 +17845,12 ##
** the remaining columns.
*/
if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+#ifdef SQLITE_IMPORT_NULL_IF_EMPTY
+ if (z==0 || z[0]=='\0') sqlite3_bind_null(pStmt, i+1);
+ else sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
+#else
sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
+#endif
if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
"filling the rest with NULL\n",

Replace last newline character in file with a semicolon

I have taken a dump of a mysql table, and I'm appending several create tables in a row - only problem is there is no semi-colon at the end of each create table.
What I'd like to do is as I capture each individual "show create table" is replace the very last "\n" with a ;
Can someone please suggest how I do this?
#!/bin/bash
> master_file.sql
> tmp.sql
while read line
do
mysql --skip-column-names -u root -pxxxxxxxxxx test -e "show create table $line" > tmp.sql
sed -i 's/\\n/\n/g' tmp.sql
#echo ';' >> tmp.sql
cat tmp.sql >> master_file.sql
echo "" >> master_file.sql
done < table_list
My output is as follows:
hours CREATE TABLE `hours` (
) DEFAULT CHARSET=latin1
;
user_session_view CREATE TABLE `user_session_view` (
) DEFAULT CHARSET=utf8
;
The semicolon should be immediately to the right of "latin1" or "utf8" in this case, but it isn't.
Taking advantage of the fact that "process substitution" removes last newline:
printf %s "$(< tmp.sql);" > tmp2.sql
In your case, you can combine sed and this:
printf %s "$(< tmp.sql);" | sed 's/\\n/\n/g' >> master_file.sql
Note that the output file should be different than the input file.
A solution with awk could be:
awk 'NR>1 {print l} {l=$0} END{ORS=""; print($0 ";")}' tmp.sql >> master_file.sql
You can do something with sed
test.txt
create table (
) default charset=latin1
create table (
) default charset=utf8
test
sed and result
sed -r 's|(charset=)(.*)|\1\2;|g' test.txt > newtest.txt
cat newtest.txt
create table (
) default charset=latin1;
create table (
) default charset=utf8;
test

How to insert round brackets in mysql table using AWK script?

I have created a table with two integer columns and wanted to insert square of each number in the table. The output should be like this..
(1,1)
(2,4)
AWK script for creating database table...
BEGIN {
system("echo 'create table square (col1 INT, col2 INT);' | mysql -u root -D database")
}
I want to display the output with brackets and commas into two integer columns and want to write the AWK script to insert the data into the above table
This is my AWK script.
BEGIN {
for (i=1; i<=10; i++)
print "(" i "," i*i ")";
}
Maybe you forgot the "values" key before the bracket??
echo "line" > file.dat
awk 'BEGIN{
system("echo 'create table square (col1 INT, col2 INT);' | mysql -u root -D database")
for (i=1; i<=10; i++)
{
print " insert into square values (" i "," i*i ");"
}
}' file.dat > querys.sql
cat querys.sql
the insert into SQL method is suposed to have the values key http://www.w3schools.com/sql/sql_insert.asp
EDIT some improvements to make it in just one command:
> file.dat
> querys.sql
awk 'BEGIN{
system("echo \"create table square (col1 INT, col2 INT);\" | mysql -u root -D database")
for (i=1; i<=10; i++)
{
print " insert into square values (" i "," i*i ");" >> "querys.sql"
}
}
END{
system("mysql -u root -D database < querys.sql")
}' file.dat
Hope it helps