Many of the answers here have serious flaws...
First don't try to parse an open ended sql script yourself! If you think that is easily done, you aren't aware of how robust and complicated sql can be. Serious sql scripts certainly involve statements and procedure definitions spanning multiple lines. It is also common to explicitly declare and change delimiters the in middle of your scripts. You can also nest source commands within each other. For so many reasons, you want to run the script through the MySQL client and allow it to handle the heavy lifting. Trying to reinvent that is fraught peril and a huge waste of time. Maybe if you are the only one writing these scripts, and you are not writing anything sophisticated you could get away with that, but why limit yourself to such a degree? What about machine generated scripts, or those written by other developers?
The answer from @jdferreira is on the right track, but also has problems and weaknesses. The most significant is that a security hole is being opened up by sending the connection parameters to the process in that manner.
Here's a solution / example for your copy & paste pleasure. My extended discussion follows:
First, create a separate config file to save your user name and password.
db-creds.cfg
[client]
user = XXXXXXX
password = YYYYYYY
Slap the right file system permissions on that, so the python process can read from it, but no one can view that who should not be able to.
Then, use this Python (in my example case the creds file is adjacent to the py script):
#!/usr/bin/python
import os
import sys
import MySQLdb
from subprocess import Popen, PIPE, STDOUT
__MYSQL_CLIENT_PATH = "mysql"
__THIS_DIR = os.path.dirname( os.path.realpath( sys.argv[0] ) )
__DB_CONFIG_PATH = os.path.join( __THIS_DIR, "db-creds.cfg" )
__DB_CONFIG_SECTION = "client"
__DB_CONN_HOST = "localhost"
__DB_CONN_PORT = 3306
# ----------------------------------------------------------------
class MySqlScriptError( Exception ):
def __init__( self, dbName, scriptPath, stdOut, stdErr ):
Exception.__init__( self )
self.dbName = dbName
self.scriptPath = scriptPath
self.priorOutput = stdOut
self.errorMsg = stdErr
errNumParts = stdErr.split("(")
try : self.errorNum = long( errNumParts[0].replace("ERROR","").strip() )
except: self.errorNum = None
try : self.sqlState = long( errNumParts[1].split(")")[0].strip() )
except: self.sqlState = None
def __str__( self ):
return ("--- MySqlScriptError ---\n" +
"Script: %s\n" % (self.scriptPath,) +
"Database: %s\n" % (self.dbName,) +
self.errorMsg )
def __repr__( self ): return self.__str__()
# ----------------------------------------------------------------
def databaseLoginParms() :
from ConfigParser import RawConfigParser
parser = RawConfigParser()
parser.read( __DB_CONFIG_PATH )
return ( parser.get( __DB_CONFIG_SECTION, "user" ).strip(),
parser.get( __DB_CONFIG_SECTION, "password" ).strip() )
def databaseConn( username, password, dbName ):
return MySQLdb.connect( host=__DB_CONN_HOST, port=__DB_CONN_PORT,
user=username, passwd=password, db=dbName )
def executeSqlScript( dbName, scriptPath, ignoreErrors=False ) :
scriptDirPath = os.path.dirname( os.path.realpath( scriptPath ) )
sourceCmd = "SOURCE %s" % (scriptPath,)
cmdList = [ __MYSQL_CLIENT_PATH,
"--defaults-extra-file=%s" % (__DB_CONFIG_PATH,) ,
"--database", dbName,
"--unbuffered" ]
if ignoreErrors :
cmdList.append( "--force" )
else:
cmdList.extend( ["--execute", sourceCmd ] )
process = Popen( cmdList
, cwd=scriptDirPath
, stdout=PIPE
, stderr=(STDOUT if ignoreErrors else PIPE)
, stdin=(PIPE if ignoreErrors else None) )
stdOut, stdErr = process.communicate( sourceCmd if ignoreErrors else None )
if stdErr is not None and len(stdErr) > 0 :
raise MySqlScriptError( dbName, scriptPath, stdOut, stdErr )
return stdOut
If you want to test it out, add this:
if __name__ == "__main__":
( username, password ) = databaseLoginParms()
dbName = "ExampleDatabase"
print "MySQLdb Test"
print
conn = databaseConn( username, password, dbName )
cursor = conn.cursor()
cursor.execute( "show tables" )
print cursor.fetchall()
cursor.close()
conn.close()
print
print "-----------------"
print "Execute Script with ignore errors"
print
scriptPath = "test.sql"
print executeSqlScript( dbName, scriptPath,
ignoreErrors=True )
print
print "-----------------"
print "Execute Script WITHOUT ignore errors"
print
try : print executeSqlScript( dbName, scriptPath )
except MySqlScriptError as e :
print "dbName: %s" % (e.dbName,)
print "scriptPath: %s" % (e.scriptPath,)
print "errorNum: %s" % (str(e.errorNum),)
print "sqlState: %s" % (str(e.sqlState),)
print "priorOutput:"
print e.priorOutput
print
print "errorMsg:"
print e.errorMsg
print
print e
print
And for good measure, here's an example sql script to feed into it:
test.sql
show tables;
blow up;
show tables;
So, now for some discussion.
First, I illustrate how to use MySQLdb along with this external script execution, while storing the creds in one shared file you can use for both.
By using --defaults-extra-file
on the command line you can SECURELY pass your connection parameters in.
The combination of either --force
with stdin streaming the source command OR --execute
running the command on the outside let's you dictate how the script will run. That is by ignoring errors and continuing to run, or stopping as soon as an error occurs.
The order in which the results comeback will also be preserved via --unbuffered
. Without that, your stdout and stderr streams will be jumbled and undefined in their order, making it very hard to figure out what worked and what did not when comparing that to the input sql.
Using the Popen cwd=scriptDirPath
let's you nest source commands within one another using relative paths. If your scripts will all be in the same directory (or a known path relative to it), doing this let's you reference those relative to where the top level script resides.
Finally, I threw in an exception class which carries all the info you could possibly want about what happened. If you are not using the ignoreErrors option, one of these exceptions will be thrown in your python when something goes wrong and script has stopped running upon that error.