All major database products have tools to let you backup their databases. MySQL makes it super simple with their command line tools. But what if you want to do it with ColdFusion? Doesn't everyone want to do everything with ColdFusion? I know I do! So let's look at a quick example.
My database backup code will work like so:- Get a list of tables from a datasource.
- Select all rows from the table.
- Convert the query into WDDX.
- Zip the giant XML string and store the result.
1 <cfset datasource="blogdev">
2
3 <cfdbinfo datasource="#datasource#" name="tables" type="tables">
This code simply uses the new cfdbinfo tag to query the tables from the datasource.
2
3 <cfdbinfo datasource="#datasource#" name="tables" type="tables">
1 <!--- One struct to rule them all... --->
2 <cfset data = structNew()>
I'm going to store all my queries in one struct.
2 <cfset data = structNew()>
1 <cfloop query="tables">
2 <!--- grab all data from table --->
3 <cfquery name="getData" datasource="#datasource#">
4 select *
5 from #table_name#
6 </cfquery>
7
8 <cfset data[table_name] = getData>
9 </cfloop>
Then I loop over each table and select *. Notice I store the query into the struct. By the way - the cfdbinfo tag also lets you get the columns from a database table. But since this is a "quickie" script, I don't mind using the select *.
2 <!--- grab all data from table --->
3 <cfquery name="getData" datasource="#datasource#">
4 select *
5 from #table_name#
6 </cfquery>
7
8 <cfset data[table_name] = getData>
9 </cfloop>
1 <!--- Now serialize into one ginormous string --->
2 <cfwddx action="cfml2wddx" input="#data#" output="packet">
Then we convert the structure into one XML packet.
2 <cfwddx action="cfml2wddx" input="#data#" output="packet">
1 <!--- file to store zip --->
2 <cfset zfile = expandPath("./data.zip")>
3
4 <!--- Now zip this baby up --->
5 <cfzip action="zip" file="#zfile#" overwrite="true">
6 <cfzipparam content="#packet#" entrypath="data.packet">
7 </cfzip>
Next I store the string into a zip using the new cfzip and cfzipparam tags. Notice how I feed the string data to the zip using cfzipparam. I don't have to store the text into a temporary file.
2 <cfset zfile = expandPath("./data.zip")>
3
4 <!--- Now zip this baby up --->
5 <cfzip action="zip" file="#zfile#" overwrite="true">
6 <cfzipparam content="#packet#" entrypath="data.packet">
7 </cfzip>
1 <cfoutput>
2 I retrieved #tables.recordCount# tables from datasource #datasource# and saved it to #zfile#.
3 </cfoutput>
The last thing I do is output a simple result message so you know how much data was backed up. Here is the complete source in one listing:
2 I retrieved #tables.recordCount# tables from datasource #datasource# and saved it to #zfile#.
3 </cfoutput>
1 <cfset datasource="blogdev">
2
3 <cfdbinfo datasource="#datasource#" name="tables" type="tables">
4
5 <!--- One struct to rule them all... --->
6 <cfset data = structNew()>
7
8 <cfloop query="tables">
9 <!--- grab all data from table --->
10 <cfquery name="getData" datasource="#datasource#">
11 select *
12 from #table_name#
13 </cfquery>
14
15 <cfset data[table_name] = getData>
16 </cfloop>
17
18 <!--- Now serialize into one ginormous string --->
19 <cfwddx action="cfml2wddx" input="#data#" output="packet">
20
21 <!--- file to store zip --->
22 <cfset zfile = expandPath("./data.zip")>
23
24 <!--- Now zip this baby up --->
25 <cfzip action="zip" file="#zfile#" overwrite="true">
26 <cfzipparam content="#packet#" entrypath="data.packet">
27 </cfzip>
28
29 <cfoutput>
30 I retrieved #tables.recordCount# tables from datasource #datasource# and saved it to #zfile#.
31 </cfoutput>
2
3 <cfdbinfo datasource="#datasource#" name="tables" type="tables">
4
5 <!--- One struct to rule them all... --->
6 <cfset data = structNew()>
7
8 <cfloop query="tables">
9 <!--- grab all data from table --->
10 <cfquery name="getData" datasource="#datasource#">
11 select *
12 from #table_name#
13 </cfquery>
14
15 <cfset data[table_name] = getData>
16 </cfloop>
17
18 <!--- Now serialize into one ginormous string --->
19 <cfwddx action="cfml2wddx" input="#data#" output="packet">
20
21 <!--- file to store zip --->
22 <cfset zfile = expandPath("./data.zip")>
23
24 <!--- Now zip this baby up --->
25 <cfzip action="zip" file="#zfile#" overwrite="true">
26 <cfzipparam content="#packet#" entrypath="data.packet">
27 </cfzip>
28
29 <cfoutput>
30 I retrieved #tables.recordCount# tables from datasource #datasource# and saved it to #zfile#.
31 </cfoutput>
Comment 1 written by Jordan Clark on 28 November 2007, at 5:41 PM
Comment 2 written by Dan on 28 November 2007, at 6:02 PM
Comment 3 written by Sam Curren on 28 November 2007, at 6:39 PM
I was thinking about writing something similar to this, only generating the create and insert statements required to extract a DB in a form that could easily be moved to a different server.
Comment 4 written by todd sharp on 28 November 2007, at 8:16 PM
Comment 5 written by Raul Riera on 29 November 2007, at 2:31 AM
Am I the only one? (I know u are on a mac too Raymond)
Comment 6 written by Raymond Camden on 29 November 2007, at 6:16 AM
Comment 7 written by James Edmunds on 29 November 2007, at 10:31 AM
Comment 8 written by Raymond Camden on 29 November 2007, at 10:36 AM
Um, I'd thin it would probably time out. You could add sanity checks in there - checking record count, etc.
Comment 9 written by David Buhler on 29 November 2007, at 10:49 AM
Comment 10 written by David Buhler on 29 November 2007, at 10:49 AM
Comment 11 written by Raymond Camden on 29 November 2007, at 10:52 AM
<cfset badlist = "foo,moo">
Then in your cfloop, you just do:
<cfif not listfind(badlist, table_name)>
Ie, if you don't find the table in the bad list, carry on.
Comment 12 written by James Edmunds on 29 November 2007, at 10:59 AM
Comment 13 written by Raymond Camden on 29 November 2007, at 11:05 AM
Comment 14 written by David Buhler on 29 November 2007, at 12:29 PM
IMO, a "skip-list" seems like a great way to compromise between incremental back-ups, and full back-ups for tables that never change.
Thanks Ray!
Comment 15 written by James Harvey on 30 January 2008, at 8:32 AM
Now that said (and it works great), is it possible to to save that backup data into a file other than a wddx packet?
say like a text file perhaps that is zipped up instead?
Just Curious...
Comment 16 written by Raymond Camden on 30 January 2008, at 9:27 AM
Comment 17 written by Erik on 15 April 2008, at 11:12 AM
Comment 18 written by Raymond Camden on 15 April 2008, at 11:16 AM
Comment 19 written by ramzi on 27 April 2008, at 4:18 AM
i m gonna read a bit more about json and wddx packets :)
good one
Comment 20 written by Mike on 19 June 2008, at 5:35 AM
As an aside for bigger DB's what would you or anyone else recommend as a way of maybe splitting the file into "emailable" chunks?
Comment 21 written by Raymond Camden on 19 June 2008, at 7:05 AM
Comment 22 written by Mike on 29 August 2008, at 2:25 PM
Comment 23 written by Raymond Camden on 30 August 2008, at 9:23 AM
a) First, you want to convert the WDDX packet back into native data. That is as simple as running cfwddx again, but with action=wddx2cfml.
b) This gives us a structure with table names as keys, and queries as data. For each key you would:
c) Loop over the query and do an insert of the data back into the table. Remember that CF gives you functions to inspect queries (columnlist) and get the individual cells, so this is something you can handle easily enough. :)
Comment 24 written by Aina on 24 June 2009, at 10:14 PM
Comment 25 written by Raymond Camden on 25 June 2009, at 6:36 AM
Comment 26 written by Aina on 26 June 2009, at 7:31 PM
Thanks,
Aina.
[Add Comment] [Subscribe to Comments]