Archive for the ‘MySQL Developer’ tag
Node.js & MySQL
These are my notes for creating a small Node.js application that queries a MySQL database. The post will show you how to:
- Configure your Node.js development directory.
- Build a small application to test a MySQL connection.
- Build a small application that connects to the MySQL database and queries data.
This blog post assumes you’ve performed a global install of Node.js on a Linux server. If you’re unfamiliar with how to perform a global Node.js installation, I cover how to do it in this earlier blog post.
Before you write the Node.js applicaiton, you need to setup a db
developer directory. Then, create a node_modules
symbolic link to the /usr/local/lib/node_modules
directory in the db
directory. You can use the following command from the db
directory:
ln -s /usr/local/lib/node_modules `pwd`/node_modules |
After creating the node_modules
symbolic link, you need to run the following two npm
commands:
npm init --y npm install --save mysql |
The first command sets up a generic package.json
file, and the second adds the mysql
package and supporting packages to the package.json
file. These two steps configure the Node.js side of these examples.
They both require that you create the student
user with a native password, like so:
CREATE USER 'student'@'localhost' IDENTIFIED WITH 'mysql_native_password' BY 'student'; GRANT ALL PRIVILEGES ON studentdb.* TO 'student'@'localhost'; FLUSH PRIVILEGES; |
The following example shows you how to check a connection to the MySQL database:
const mysql = require('mysql') const connection = mysql.createConnection({ host: 'localhost', user: 'student', password: 'student', database: 'studentdb' }) connection.connect((err) => { if (err) throw err else console.log('Connected to MySQL Server!\n') console.log('User configured wiht mysql_native_password.\n'); console.log('Press Ctrl-C to terminate ...') }) |
You extend the previous example by adding a query component and returning the query result
value to the console’s log (leveraging the w3school’s Node.js and MySQL tutorial example):
const mysql = require('mysql') const connection = mysql.createConnection({ host: 'localhost', user: 'student', password: 'student', database: 'studentdb' }) connection.connect((err) => { if (err) throw err else { console.log('Connected to MySQL Server!\n') connection.query('SELECT DISTINCT item_title FROM item', function (err, result) { if (err) throw err else console.log(result) console.log('Press Ctrl-C to terminate ...') }) } }) |
It should display the following:
Connected to MySQL Server! [ RowDataPacket { item_title: 'The Hunt for Red October' }, RowDataPacket { item_title: 'Star Wars I' }, RowDataPacket { item_title: 'Star Wars II' }, RowDataPacket { item_title: 'Star Wars III' }, RowDataPacket { item_title: 'The Chronicles of Narnia' }, RowDataPacket { item_title: 'RoboCop' }, RowDataPacket { item_title: 'Pirates of the Caribbean' }, RowDataPacket { item_title: 'MarioKart' }, RowDataPacket { item_title: 'Splinter Cell' }, RowDataPacket { item_title: 'Need for Speed' }, RowDataPacket { item_title: 'The DaVinci Code' }, RowDataPacket { item_title: 'Cars' }, RowDataPacket { item_title: 'Beau Geste' }, RowDataPacket { item_title: 'I Remember Mama' }, RowDataPacket { item_title: 'Tora! Tora! Tora!' }, RowDataPacket { item_title: 'A Man for All Seasons' }, RowDataPacket { item_title: 'Hook' }, RowDataPacket { item_title: 'Around the World in 80 Days' }, RowDataPacket { item_title: 'Harry Potter and the Sorcerer\'s Stone' }, RowDataPacket { item_title: 'Camelot' }, RowDataPacket { item_title: 'Casino Royale' }, RowDataPacket { item_title: 'Die Another Day' }, RowDataPacket { item_title: 'Golden Eye' }, RowDataPacket { item_title: 'Tomorrow Never Dies' }, RowDataPacket { item_title: 'The World Is Not Enough' }, RowDataPacket { item_title: 'Brave Heart' }, RowDataPacket { item_title: 'Christmas Carol' }, RowDataPacket { item_title: 'Scrooge' }, RowDataPacket { item_title: 'Clear and Present Danger' }, RowDataPacket { item_title: 'Harry Potter and the Chamber of Secrets' }, RowDataPacket { item_title: 'Harry Potter and the Prisoner of Azkaban' }, RowDataPacket { item_title: 'Harry Potter and the Goblet of Fire' }, RowDataPacket { item_title: 'Harry Potter and the Order of the Phoenix' }, RowDataPacket { item_title: 'Harry Potter and the Half Blood Prince' }, RowDataPacket { item_title: 'Harry Potter and the Deathly Hallows, Part 1' }, RowDataPacket { item_title: 'Harry Potter and the Deathly Hallows, Part 2' }, RowDataPacket { item_title: 'Tron' }, RowDataPacket { item_title: 'The Avengers' }, RowDataPacket { item_title: 'Thor: The Dark World' } ] Press Ctrl-C to terminate ... |
As always, I hope this helps those looking to learn
MySQL 8.0.20 JDBC
In April, when I updated from MySQL 8.0.17 to MySQL 8.0.19, I found that my Java connection example failed. That’s because of a change in the JDBC driver, which I blogged about then. Starting yesterday, I began updating a base Fedora 30 configuration again to MySQL 8.0.20. I wrote a testing program for the Java JDBC file last time, and when I ran it this time it told me that I didn’t have the JDBC driver installed, or in the $CLASSPATH
. My Java diagnostic script, MySQLDriver.java
, returned the following error message:
Error: Could not find or load main class MySQLDriver |
The Java JDBC test program code is in the prior post. It simply loads the user, password, database, host, and port statically for my student image, which you can update for your MySQL deployment.
You can find the MySQL 8.0 Connector/J installation in Chapter 4 of the MySQL Connector/J 8.0 Developer Guide. The link to page where you can download the Connector/J driver is in Chapter 4, but to save time you can click on the following download link. You’ll find Ubuntu, Debian, SUSE Enterprise Linux Server, Red Hat Enterprise Linux/Oracle Linux, Fedora, Platform Independent, and Source Code at that download link.
I downloaded Fedora 30, 31, and 32 because eventually they get deprecated and sometimes I need the deprecated versions. Since Python 2.7 is deprecated, keeping an old Fedora 30 with both Python 2.7 and 3.7 is very attractive as a developer, keeping these Java libraries is also a potential convenience. On the Fedora 30 platform, you should download:
mysql-connector-java-8.0.20-1.fc30.noarch.rpm |
You install the RPM package with the following syntax:
rpm -ivh mysql-connector-java-8.0.20-1.fc30.noarch.rpm |
It installs a single mysql-connector-java.jar
file in the /usr/share/java
directory. Then, you need to export the Java Archive file like so in your .bashrc
file:
# Set the Java CLASSPATH path. export CLASSPATH=/usr/share/java/mysql-connector-java.jar:. |
Now, my MySQLDriver Java program returned:
Cannot connect to database server: The server time zone value 'MDT' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support. |
As qualified in the prior post, it required me to add the following line to my /etc/my.cnf
configuration file, which synchronizes the database’s timezone with the operating system.
# Synchronize the MySQL clock with the computer system clock. default-time-zone='+00:00' |
You need to restart the MySQL Daemon to apply the change, like:
sudo systemctl restart mysqld.service |
Then, running my MySQL Driver connection test program worked like a charm. It returns the following:
Database connection established MySQLDriver Version [8.0.20] Database connection terminated |
As always, I hope this helps those looking for a quick solution.
MySQL 8.0.20 Update
After I updated a Fedora 30 instance, I could no longer connect to the MySQL database. An attempt to connect raised the following error:
Error: Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2) |
My guess was correct, the mysqld.service
got removed during the update (a synonym for upgrade). So, I ran the following command as a sudoer user:
sudo systemctl enable mysqld.service |
It creates the following symbolic link:
Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /usr/lib/systemd/system/mysqld.service. |
That would start the MySQL Daemon (mysqld
) on the next restart of the OS. However, I didn’t want to restart to have access to the service. I simply started it with the following command:
sudo systemctl start mysqld.service |
Then, I could connect to the MySQL database. As always, I hope this helps those looking for a solution.
MySQL Linux to Windows
My students want to transfer their course setup MySQL files from the Linux VM that I provide to Microsoft Windows 10. This post explains how because I found a couple small errors in the Google’d posts they would most likely see.
The first step is required because when I didn’t assign a name or domain to the the Fedora VM, which allows it to run as localhost on any student machine. In tandem, I didn’t assign a static IP address but opted for dynamic IP assignment. That means, the first step to securely copy the files requires you to find the assigned IP address. You can do that with the following Linux command:
ifconfig -a | grep 'inet[[:blank:]]' | head -1 | cut -c 14-30 |
It would return something like:
192.168.147.198 |
After you have discovered the IP address, you need to download PuTTy from their web site because includes the pscp (PuTTy Secure Copy) utility. I recommend you click on the MSI (Microsoft Installer Package) file, and install it on your Windows machine. As a rule, you should accept the default location, which is C:\Program Files\PuTTy
.
While you could alter your system-level %PATH%
environment variable after you install the software, I recommend you only include it in the %PATH%
within the scope of a Command (cmd
) shell. Navigate to your Windows Start and enter cmd
in the search field. It should launch the Command Prompt terminal, which is a terminal emulator.
In the terminal editor, type the following case sensitive command to add the PuTTy directory to your path (yes, it’s case sensitive):
SET PATH=%PATH%;%ProgramFiles%\PuTTy |
Now, you can securely copy the files and directory structure from Linux to Windows with the following command (where you replace the traditional server name with the dynamically assigned IP address). You should also be in the target directory where you want the files and directories copied:
C:\Data\cit225>pscp -r student@192.168.147.198:/home/student/Data/cit225/mysql . |
After entering the password for the student on the Linux VM, you should see the following copy over:
Raiders2.png | 99 kB | 99.5 kB/s | ETA: 00:00:00 | 100% LordOfTheRings3.png | 119 kB | 119.8 kB/s | ETA: 00:00:00 | 100% HarryPotter4.png | 103 kB | 103.9 kB/s | ETA: 00:00:00 | 100% Raiders1.png | 92 kB | 92.4 kB/s | ETA: 00:00:00 | 100% Raiders3.png | 123 kB | 123.9 kB/s | ETA: 00:00:00 | 100% LordOfTheRings2.png | 111 kB | 111.7 kB/s | ETA: 00:00:00 | 100% LordOfTheRings1.png | 103 kB | 104.0 kB/s | ETA: 00:00:00 | 100% HarryPotter2.png | 118 kB | 118.7 kB/s | ETA: 00:00:00 | 100% HarryPotter7.png | 150 kB | 150.2 kB/s | ETA: 00:00:00 | 100% HarryPotter3.png | 106 kB | 106.1 kB/s | ETA: 00:00:00 | 100% HarryPotter5.png | 82 kB | 82.5 kB/s | ETA: 00:00:00 | 100% HarryPotter6.png | 129 kB | 129.9 kB/s | ETA: 00:00:00 | 100% HarryPotter1.png | 118 kB | 118.8 kB/s | ETA: 00:00:00 | 100% HarryPotter8.png | 150 kB | 150.9 kB/s | ETA: 00:00:00 | 100% HarryPotter8.txt | 8 kB | 8.5 kB/s | ETA: 00:00:00 | 100% HarryPotter3.txt | 5 kB | 5.8 kB/s | ETA: 00:00:00 | 100% HarryPotter5.txt | 7 kB | 7.9 kB/s | ETA: 00:00:00 | 100% HarryPotter1.txt | 6 kB | 6.6 kB/s | ETA: 00:00:00 | 100% HarryPotter2.txt | 7 kB | 7.8 kB/s | ETA: 00:00:00 | 100% Raiders3.txt | 5 kB | 5.6 kB/s | ETA: 00:00:00 | 100% HarryPotter4.txt | 7 kB | 7.5 kB/s | ETA: 00:00:00 | 100% HarryPotter7.txt | 5 kB | 5.4 kB/s | ETA: 00:00:00 | 100% HarryPotter6.txt | 7 kB | 7.4 kB/s | ETA: 00:00:00 | 100% LOTRFellowship.txt | 4 kB | 5.0 kB/s | ETA: 00:00:00 | 100% apply_store_base.sql | 1 kB | 1.6 kB/s | ETA: 00:00:00 | 100% query_membership.sql | 0 kB | 0.3 kB/s | ETA: 00:00:00 | 100% apply_mysql_lab1.sql | 1 kB | 1.9 kB/s | ETA: 00:00:00 | 100% configure_mysql_web.sql | 37 kB | 37.1 kB/s | ETA: 00:00:00 | 100% seed_mysql_store_ri2.sql | 58 kB | 58.5 kB/s | ETA: 00:00:00 | 100% cleanup_mysql_store.sql | 5 kB | 5.4 kB/s | ETA: 00:00:00 | 100% create_mysql_store_ri2.sq | 21 kB | 21.1 kB/s | ETA: 00:00:00 | 100% |
My students will need to repeat this step to transfer all of the sample PHP files that demonstrate web application patterns. They also need to inspect individual files to ensure any path referencing commands are manually converted to their new Windows equivalent.
They can move the physical files as the root
superuser with the following pscp
command provide you haven’t stored the files somewhere other than the default location:
C:\Data\temp>pscp -r root@192.168.147.198:/var/lib/mysql . |
As always, I hope this helps those trying to sort things out.
Java & MySQL 8.0.19
It’s the in-between term time and we’re all stuck at home. I decided to update the image for my Fedora 30 virtual machine. I had a work around to the update issue that I had encountered last October in Bug #96969 but it was not required with the current version. However, after updating from MySQL 8.0.17 to MySQL 8.0.19, I found that my Java connection example failed.
The $CLASSPATH
value was correct:
/usr/share/java/mysql-connector-java.jar:. |
The first error that I got was the my reference to MySQL JDBC driver was incorrect. The error message is quite clear:
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. Cannot connect to database server: The server time zone value 'MDT' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support. |
I changed the MySQL Driver reference as instructed by the error message:
29 30 31 | // Create instance of MySQLDriver. Class.forName ("com.mysql.cj.jdbc.Driver").newInstance(); conn = DriverManager.getConnection (url, username, password); |
After the change, I got the following error while retesting my little MySQL Java driver connection test program. Initially, I thought this required a change in the Java environment but that wasn’t it.
Cannot connect to database server: The server time zone value 'MDT' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support. |
It required me to add the following line to my /etc/my.cnf configuration file, which synchronizes the database’s timezone with the operating system.
# Synchronize the MySQL clock with the computer system clock. default-time-zone='+00:00' |
Then, running my MySQL Driver connection test program worked like a charm. It returns the following:
Database connection established MySQLDriver Version [8.0.19] Database connection terminated |
Here’s the MySQL Connector Java code if you’d like to use the MySQLDriver.java
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | // Import classes. import java.sql.*; /* You can't include the following on Linux without raising an exception. */ // import com.mysql.jdbc.Driver; public class MySQLDriver { public MySQLDriver() { /* Declare variables that require explicit assignments because they're addressed in the finally block. */ Connection conn = null; Statement stmt = null; ResultSet rset = null; /* Declare other variables. */ String url; String username = "student"; String password = "student"; String database = "studentdb"; String hostname = "localhost"; String port = "3306"; String sql; /* Attempt a connection. */ try { // Set URL. url = "jdbc:mysql://" + hostname + ":" + port + "/" + database; // Create instance of MySQLDriver. Class.forName ("com.mysql.cj.jdbc.Driver").newInstance(); conn = DriverManager.getConnection (url, username, password); // Query the version of the database. sql = "SELECT version()"; stmt = conn.createStatement(); rset = stmt.executeQuery(sql); System.out.println ("Database connection established"); // Read row returns for one column. while (rset.next()) { System.out.println("MySQLDriver Version [" + rset.getString(1) + "]"); } } catch (SQLException e) { System.err.println ("Cannot connect to database server:"); System.out.println(e.getMessage()); } catch (ClassNotFoundException e) { System.err.println ("Cannot find MySQL driver class:"); System.out.println(e.getMessage()); } catch (InstantiationException e) { System.err.println ("Cannot instantiate class:"); System.out.println(e.getMessage()); } catch (IllegalAccessException e) { System.err.println ("Illegal access exception:"); System.out.println(e.getMessage()); } finally { if (conn != null) { try { rset.close(); stmt.close(); conn.close(); System.out.println ("Database connection terminated"); } catch (Exception e) { /* ignore close errors */ } } } } /* Unit test. */ public static void main(String args[]) { new MySQLDriver(); } } |
As always, I hope this helps those who encounter similar problems.
Session Variables
In MySQL and Oracle, you set a session variable quite differently. That means you should expect there differences between setting a session variable in Postgres. This blog post lets you see how to set them in all three databases. I’m always curious what people think but I’m willing to bet that MySQL is the simplest approach. Postgres is a bit more complex because you must use a function call, but Oracle is the most complex.
The difference between MySQL and Postgres is an “@
” symbol versus a current_setting()
function call. Oracle is more complex because it involves the mechanics in Oracle’s sqlplus
shell, SQL dialect, and PL/SQL language (required to assign a value to a variable).
MySQL
MySQL lets you declare a session variable in one step and use it one way in a SQL statement or stored procedure.
- You set a session variable on a single line with the following command:
SET @my_variable_name := 'My Value';
- You can query a variable from the pseudo table
dual
or as a comparison value in theSELECT
-listSELECT @my_variable_name AS "The Value" FROM dual;
or
WHERE
clauseSELECT column_name FROM table_name WHERE column_name = @my_variable_name;
Postgres
Postgres lets you declare a session variable in one step and use it one way in a SQL statement or stored procedure.
- You set a session variable in a single line. It iss critical to note that you must use double quotes around the session variable name and single quotes for the value. You raise an error when you use a single quote instead a double quote around the session variable name. The syntax is:
SET SESSION "videodb.table_name" = 'new_hire';
- You can query a variable from the pseudo table
dual
or as a comparison value in theSELECT
-list with thecurrent_setting()
function call.SELECT current_setting('videodb.table_name') AS "The Value";
or
WHERE
clauseSELECT column_name FROM table_name WHERE column_name = current_setting('videodb.table_name');
Oracle
There are two steps required to declare a session variable in Oracle. First, you need to define the variable in the SQL*Plus session. Oracle lets you define a variable like you would define a variable in the C language, using extern
before the variable’s type. Second, you assign a value to the session variable in an anonymous PL/SQL block. There is no single line statement to declare a variable with an initial value.
- You set a session variable by using the
VARIABLE
keyword, a variable name, and data type. The supported data types are:BLOB
,BFILE
,BINARY_DOUBLE
,BINARY_FLOAT
,CHAR
,CLOB
,NCHAR
,NCLOB
,NVARCHAR2
,REFCURSOR
, andVARCHAAR2
. You define a variable with the following syntax:VARIABLE bv_variable_name VARCHAR2(30)
- You assign a value to the bind variable inside an anonymous block by prefacing the variable name with a colon. You assign values inside PL/SQL with the walrus operator (
:=
) and a string enclosed by single quotes. Anonymous blocks start with aBEGIN
and end with anEND
followed by a semicolon (;
) and a forward slash (/
) to dispatch the block for execution. The following example shows a full block:BEGIN :bv_variable_name := 'Some Value'; END; /
- You can query any declared variable from the pseudo table
dual
or as a comparison value in theSELECT
-listSELECT :bv_variable_name FROM dual;
or
WHERE
clauseSELECT column_name FROM table_name WHERE column_name = :bv_variable_name;
mysqli Strict Standards
Six years ago I wrote a common lookup post to illustrate the effectiveness of things used throughout your applications. Now, I’m updating my student image with a more complete solution to show how to avoid update anomalies.
In the prior post, I used a while
loop in PHP, like the following:
do { ... } while($stmt->next_result()); |
Using PHP Version 7.3.8 and MySQL 8.0.16, that now raises the following error message:
Strict Standards: mysqli_stmt::next_result(): There is no next result set. Please, call mysqli_stmt_more_results()/mysqli_stmt::more_results() to check whether to call this function/method in /var/www/html/app/library.inc on line 81 |
You can see this type of error when you set the following parameters in your file during testing:
ini_set('display_errors',1); ini_set('display_startup_errors',1); error_reporting(E_ALL); |
You can read more about error handling at this web page. The new and strict compliance standard for mysqli
managing rows is:
do { ... } while($stmt->more_result()); |
As always, I hope this helps those looking for an answer.
MySQL Update in mysqli
Somebody didn’t like the MySQLi Update Query example on the tutorialspoint.com website because it use the procedure mysqli_query
style. Here’s a simple example of using the object-oriented method version. More or less, instead of query it uses the more intuitive execute()
method.
The update_member
function contains the logic and below it is a call to the test the function. It relies on a MySQLCredentials.inc
file that contains the hostname, user name, password, and database name. You can create create member
table, like my example in MySQL 8, or any other table in your MySQL database.
<?php /* || Function Name: update_member */ function update_member($account_number, $member_type, $credit_card_number, $credit_card_type) { // Include the credentials file if omitted. include_once("MySQLCredentials.inc"); // Assign credentials to connection. $mysqli = new mysqli(HOSTNAME, USERNAME, PASSWORD, DATABASE); // Check for connection error and print message. if ($mysqli->connect_errno) { print $mysqli->connect_error."<br />"; print "Connection not established ...<br />"; } else { // Initial statement. $stmt = $mysqli->stmt_init(); /* Disabling auto commit when you want two or more statements executed as a set. || ------------------------------------------------------------ || You would add the following command to disable the default || of auto commit. || ------------------------------ || $mysqli->autocommit(FALSE); || ------------------------------------------------------------ */ // Declare a static query. $sql = "UPDATE member\n" . "SET member_type = ?\n" . ", credit_card_number = ?\n" . ", credit_card_type = ?\n" . "WHERE account_number = ?\n"; /* Prepare statement. || ------------------------------------------------------------ || Please note that the bind_param method is a position || rather than named notation, which means you must provide || the variables in the same order as they are found in || the defined $sql variable as "?". || ------------------------------------------------------------ || print($sql); || print("Member Type: [1][".$member_type."]\n"); || print("Credit Card No: [2][".$credit_card_number."]\n"); || print("Credit Card Type: [3][".$credit_card_type."]\n"); || print("Account Number: [4][".$account_number."]\n"); || ------------------------------------------------------------ */ if ($stmt->prepare($sql)) { $stmt->bind_param("ssss",$member_type,$credit_card_number,$credit_card_type,$account_number); } // Attempt query and exit with failure before processing. if (!$stmt->execute()) { // Print failure to resolve query message. print $mysqli->error."<br />"; print "Failed to resolve query ...<br />"; } else { /* Manually commiting writes when you have disabled the || default auto commit setting, explained above. || ------------------------------------------------------------ || You would add the following command to commit the || transaction. || ------------------------------ || $mysqli->commit(); || ------------------------------------------------------------ */ } } } // Test case update_member('US00011', '1006', '6011-0000-0000-0078', '1007'); ?> |
I put this logic in a function.php file. If you do the same, you can run the test case like this from the command line:
php function.sql |
As always, I hope this helps.
Python MySQL Query
Somebody asked me how to expand a prior example with the static variables so that it took arguments at the command line for the variables. This example uses Python 3 new features in the datetime
package.
There’s a small trick converting the string
arguments to date
data types. Here’s a quick example that shows you how to convert the argument list into individual date
data type variables:
#!/usr/bin/python3 # include standard modules import sys from datetime import datetime # Capture argument list. fullCmdArguments = sys.argv # Assignable variables. beginDate = "" endDate = "" # Assign argument list to variable. argumentList = fullCmdArguments[1:] # Enumerate through the argument list where beginDate precedes endDate as strings. try: for i, s in enumerate(argumentList): if (i == 0): beginDate = datetime.date(datetime.fromisoformat(s)) elif (i == 1): endDate = datetime.date(datetime.fromisoformat(s)) except ValueError: print("One of the first two arguments is not a valid date (YYYY-MM-DD).") # Print the processed values and types. print("Begin Date: [",beginDate,"][",type(beginDate),"]") print("End Date: [",endDate,"][",type(endDate),"]") |
Assume you call this arguments.py
. Then, you call it with valid conforming date format value like the following command-line example:
./arguments.py 2001-01-01 2003-12-31 |
It returns the arguments after they have been converted to date
data types. The results should look like this:
Begin Date: 1991-01-01 [ <class 'datetime.date'> ] End Date: 2004-12-31 [ <class 'datetime.date'> ] |
The next Python example accepts dynamic arguments at the command line to query the MySQL database:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #!/usr/bin/python3 # Import the library. import sys import mysql.connector from datetime import datetime from datetime import date from mysql.connector import errorcode # Capture argument list. fullCmdArguments = sys.argv # Assignable variables. start_date = "" end_date = "" # Assign argument list to variable. argumentList = fullCmdArguments[1:] # Check and process argument list. # ============================================================ # If there are less than two arguments provide default values. # Else enumerate and convert strings to dates. # ============================================================ if (len(argumentList) < 2): # Set a default start date. if (isinstance(start_date,str)): start_date = date(1980, 1, 1) # Set the default end date. if (isinstance(end_date,str)): end_date = datetime.date(datetime.today()) else: # Enumerate through the argument list where beginDate precedes endDate as strings. try: for i, s in enumerate(argumentList): if (i == 0): start_date = datetime.date(datetime.fromisoformat(s)) elif (i == 1): end_date = datetime.date(datetime.fromisoformat(s)) except ValueError: print("One of the first two arguments is not a valid date (YYYY-MM-DD).") # Attempt the query. # ============================================================ # Use a try-catch block to manage the connection. # ============================================================ try: # Open connection. cnx = mysql.connector.connect(user='student', password='student', host='127.0.0.1', database='studentdb') # Create cursor. cursor = cnx.cursor() # Set the query statement. query = ("SELECT CASE " " WHEN item_subtitle IS NULL THEN item_title " " ELSE CONCAT(item_title,': ',item_subtitle) " " END AS title, " "release_date " "FROM item " "WHERE release_date BETWEEN %s AND %s " "ORDER BY item_title") # Execute cursor. cursor.execute(query, (start_date, end_date)) # Display the rows returned by the query. for (item_name, release_date) in cursor: print("{}, {:%d-%b-%Y}".format(item_name, release_date)) # Handle exception and close connection. # ============================================================ except mysql.connector.Error as e: if e.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("Something is wrong with your user name or password") elif e.errno == errorcode.ER_BAD_DB_ERROR: print("Database does not exist") else: print("Error code:", e.errno) # error number print("SQLSTATE value:", e.sqlstate) # SQLSTATE value print("Error message:", e.msg) # error message # Close the connection when the try block completes. finally: cnx.close() |
You can call the python-mysql-query.py
program with the following syntax:
./python-mysql-query.py 2001-01-01 2003-12-31 |
It returns the films between 1 Jan 2001 and 31 Dec 2003, like this:
Clear and Present Danger: Special Collector's Edition, 06-May-2003 Die Another Day: 2-Disc Ultimate Version, 03-Jun-2003 Die Another Day, 03-Jun-2003 Die Another Day, 03-Jun-2003 Golden Eye, 03-Jun-2003 Golden Eye: Special Edition, 03-Jun-2003 Harry Potter and the Chamber of Secrets, 28-May-2002 Harry Potter and the Chamber of Secrets: Two-Disc Special Edition, 28-May-2002 Harry Potter and the Sorcerer's Stone, 28-May-2002 Harry Potter and the Sorcerer's Stone: Two-Disc Special Edition, 28-May-2002 Harry Potter and the Sorcerer's Stone: Full Screen Edition, 28-May-2002 MarioKart: Double Dash, 17-Nov-2003 Pirates of the Caribbean, 30-Jun-2003 RoboCop, 24-Jul-2003 Splinter Cell: Chaos Theory, 08-Apr-2003 Star Wars II: Attack of the Clones, 16-May-2002 Star Wars II: Attack of the Clones, 16-May-2002 The Chronicles of Narnia: The Lion, the Witch and the Wardrobe, 30-Jun-2003 The Chronicles of Narnia: The Lion, the Witch and the Wardrobe, 16-May-2002 |
As always, I hope this helps somebody who wants to learn how to use Python with the MySQL database.
MySQL Python Connector
While building my student image on Fedora 30, I installed the MySQL PHP Connector (php-mysqlndrp) but neglected to install the Python Connector. This adds the installation and basic test of the Python Connector to the original blog post.
You use the following command with a wildcard as a privileged user. The wildcard is necessary because you need to load two libraries to support Python 2.7 and 3.7, which are installed on Fedora 30. You also need to be the root user or a user that is found in the sudoer’s list:
yum install -y mysql-connector-python* |
Display detailed console log →
Last metadata expiration check: 0:35:46 ago on Tue 20 Aug 2019 05:36:29 PM MDT. Dependencies resolved. ===================================================================================================================================== Package Architecture Version Repository Size ===================================================================================================================================== Installing: mysql-connector-python x86_64 8.0.17-1.fc30 mysql-connectors-community 435 k mysql-connector-python-cext x86_64 8.0.17-1.fc30 mysql-connectors-community 7.7 M mysql-connector-python3 x86_64 8.0.17-1.fc30 mysql-connectors-community 429 k mysql-connector-python3-cext x86_64 8.0.17-1.fc30 mysql-connectors-community 7.7 M Installing dependencies: python2-protobuf noarch 3.6.1-3.fc30 fedora 563 k python3-protobuf noarch 3.6.1-3.fc30 fedora 568 k Transaction Summary ===================================================================================================================================== Install 6 Packages Total download size: 17 M Installed size: 89 M Downloading Packages: (1/6): python3-protobuf-3.6.1-3.fc30.noarch.rpm 1.0 MB/s | 568 kB 00:00 (2/6): python2-protobuf-3.6.1-3.fc30.noarch.rpm 994 kB/s | 563 kB 00:00 (3/6): mysql-connector-python-8.0.17-1.fc30.x86_64.rpm 481 kB/s | 435 kB 00:00 (4/6): mysql-connector-python3-8.0.17-1.fc30.x86_64.rpm 612 kB/s | 429 kB 00:00 (5/6): mysql-connector-python-cext-8.0.17-1.fc30.x86_64.rpm 3.8 MB/s | 7.7 MB 00:02 (6/6): mysql-connector-python3-cext-8.0.17-1.fc30.x86_64.rpm 4.2 MB/s | 7.7 MB 00:01 ------------------------------------------------------------------------------------------------------------------------------------- Total 5.4 MB/s | 17 MB 00:03 warning: /var/cache/dnf/mysql-connectors-community-8bcc2bd350b53f70/packages/mysql-connector-python-8.0.17-1.fc30.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY MySQL Connectors Community 7.0 MB/s | 27 kB 00:00 Importing GPG key 0x5072E1F5: Userid : "MySQL Release Engineering <mysql-build@oss.oracle.com>" Fingerprint: A4A9 4068 76FC BD3C 4567 70C8 8C71 8D3B 5072 E1F5 From : /etc/pki/rpm-gpg/RPM-GPG-KEY-mysql Key imported successfully Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : python3-protobuf-3.6.1-3.fc30.noarch 1/6 Installing : python2-protobuf-3.6.1-3.fc30.noarch 2/6 Installing : mysql-connector-python-8.0.17-1.fc30.x86_64 3/6 Installing : mysql-connector-python3-8.0.17-1.fc30.x86_64 4/6 Installing : mysql-connector-python3-cext-8.0.17-1.fc30.x86_64 5/6 Installing : mysql-connector-python-cext-8.0.17-1.fc30.x86_64 6/6 Running scriptlet: mysql-connector-python-cext-8.0.17-1.fc30.x86_64 6/6 Verifying : python2-protobuf-3.6.1-3.fc30.noarch 1/6 Verifying : python3-protobuf-3.6.1-3.fc30.noarch 2/6 Verifying : mysql-connector-python-8.0.17-1.fc30.x86_64 3/6 Verifying : mysql-connector-python-cext-8.0.17-1.fc30.x86_64 4/6 Verifying : mysql-connector-python3-8.0.17-1.fc30.x86_64 5/6 Verifying : mysql-connector-python3-cext-8.0.17-1.fc30.x86_64 6/6 Installed: mysql-connector-python-8.0.17-1.fc30.x86_64 mysql-connector-python-cext-8.0.17-1.fc30.x86_64 mysql-connector-python3-8.0.17-1.fc30.x86_64 mysql-connector-python3-cext-8.0.17-1.fc30.x86_64 python2-protobuf-3.6.1-3.fc30.noarch python3-protobuf-3.6.1-3.fc30.noarch Complete! |
Leveraging the MySQL Connector/Python Coding Examples documentation, Section 5.1 Connecting to MySQL Using Connector/Python here’s a test of the connection to MySQL 8.
# Import the library. import mysql.connector from mysql.connector import errorcode try: # Open connection. cnx = mysql.connector.connect(user='student', password='student', host='127.0.0.1', database='studentdb') # Print the value. print("Database connection resolved.") # Handle exception and close connection. except mysql.connector.Error as e: if e.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("Something is wrong with your user name or password") elif e.errno == errorcode.ER_BAD_DB_ERROR: print("Database does not exist") else: print(e) # Close the connection when the try block completes. else: cnx.close() |
Leveraging the MySQL Connector/Python Coding Examples documentation, Section 5.4 Querying Data Using Connector/Python here’s a test of the connection to MySQL 8.
# Import the library. import datetime import mysql.connector from mysql.connector import errorcode try: # Open connection. cnx = mysql.connector.connect(user='student', password='student', host='127.0.0.1', database='studentdb') # Create cursor. cursor = cnx.cursor() # Set the query statement. query = ("SELECT " "CASE " " WHEN item_subtitle IS NULL THEN item_title " " ELSE CONCAT(item_title,': ',item_subtitle) " "END AS title, " "release_date " "FROM item " "WHERE release_date BETWEEN %s AND %s " "ORDER BY item_title") # Set the start and end date. start_date = datetime.date(1991, 1, 1) end_date = datetime.date(2004, 12, 31) # Execute cursor. cursor.execute(query, (start_date, end_date)) # Display the rows returned by the query. for (item_name, release_date) in cursor: print("{}, {:%d %b %Y}".format(item_name, release_date)) # Close cursor. cursor.close() # ------------------------------------------------------------ # Handle exception and close connection. except mysql.connector.Error as e: if e.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("Something is wrong with your user name or password") elif e.errno == errorcode.ER_BAD_DB_ERROR: print("Database does not exist") else: print "Error code:", e.errno # error number print "SQLSTATE value:", e.sqlstate # SQLSTATE value print "Error message:", e.msg # error message # Close the connection when the try block completes. else: cnx.close() |
If you run the above in Python 2.7 it works fine. It fails to parse successfully in Python 3.x because the print()
function requires the parentheses all the time. You would need to re-write the except
block, like this with the parentheses:
# Handle exception and close connection. except mysql.connector.Error as e: if e.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("Something is wrong with your user name or password") elif e.errno == errorcode.ER_BAD_DB_ERROR: print("Database does not exist") else: print("Error code:", e.errno) # error number print("SQLSTATE value:", e.sqlstate) # SQLSTATE value print("Error message:", e.msg) # error message |
While it works without the parentheses in Python 2.7, it also works with the parentheses. That means the best practice is to write cross compatible code by always using the parentheses with the print()
function.
As always, I hope this helps somebody.j