308 lines
13 KiB
Rexx
308 lines
13 KiB
Rexx
|
/*----------------------------------------------------------------------------*/
|
||
|
/* Copyright (c) 2006-2024 Rexx Language Association. All rights reserved. */
|
||
|
/* */
|
||
|
/* This program and the accompanying materials are made available under */
|
||
|
/* the terms of the Common Public License v1.0 which accompanies this */
|
||
|
/* distribution. A copy is also available at the following address: */
|
||
|
/* https://www.oorexx.org/license.html */
|
||
|
/* */
|
||
|
/* Redistribution and use in source and binary forms, with or */
|
||
|
/* without modification, are permitted provided that the following */
|
||
|
/* conditions are met: */
|
||
|
/* */
|
||
|
/* Redistributions of source code must retain the above copyright */
|
||
|
/* notice, this list of conditions and the following disclaimer. */
|
||
|
/* Redistributions in binary form must reproduce the above copyright */
|
||
|
/* notice, this list of conditions and the following disclaimer in */
|
||
|
/* the documentation and/or other materials provided with the distribution. */
|
||
|
/* */
|
||
|
/* Neither the name of Rexx Language Association nor the names */
|
||
|
/* of its contributors may be used to endorse or promote products */
|
||
|
/* derived from this software without specific prior written permission. */
|
||
|
/* */
|
||
|
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
|
||
|
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
|
||
|
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */
|
||
|
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */
|
||
|
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
|
||
|
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */
|
||
|
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */
|
||
|
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */
|
||
|
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */
|
||
|
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */
|
||
|
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
|
||
|
/* */
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
/*
|
||
|
purpose: Demonstrate how one can interact with Jet (MS Access) databases using ActiveX/OLE
|
||
|
needs: MS Access resp. the JetEngine installed
|
||
|
|
||
|
notes: This sample can only run on a 32-bit ooRexx. The Jet Engine can not
|
||
|
read Office 2007 database file formats. Therefore, we check if
|
||
|
Access is 2007, and if so, force the database to be created in
|
||
|
Access 2003 format. Look at the createDataBaseFile() routine for
|
||
|
how this is done.
|
||
|
|
||
|
This sample uses some public routines from the oleUtils framework.
|
||
|
getAddressingMode()
|
||
|
createOleObject()
|
||
|
|
||
|
links: <https://docs.microsoft.com/en-us/office/vba/api/overview/access>
|
||
|
<https://docs.microsoft.com/en-us/office/vba/access/concepts/miscellaneous/concepts-access-vba-reference>
|
||
|
<https://docs.microsoft.com/en-us/office/vba/api/overview/access/object-model>
|
||
|
*/
|
||
|
|
||
|
-- This first function just checks to be sure the sample can execute on the
|
||
|
-- current system. Look at the function code at the end of the file for the
|
||
|
-- details. The function returns the oleObject, access application object, on
|
||
|
-- success and .nil if this sample can not run on the current system.
|
||
|
accessApplication = checkSystem()
|
||
|
if accessApplication == .nil then return
|
||
|
|
||
|
|
||
|
dbFileName=directory()"\ooRexxDemo.mdb" -- define database file name
|
||
|
call createDataBaseFile dbFileName, accessApplication -- create database file, if necessary
|
||
|
|
||
|
conn=.oleobject~new("ADODB.Connection") -- create an ADO conncection to the database
|
||
|
conn~provider="Microsoft.Jet.OLEDB.4.0" -- tell the connection what driver to use
|
||
|
|
||
|
conn~open(dbFileName) -- open a connection to the database
|
||
|
|
||
|
call dropTable conn -- make sure table is dropped
|
||
|
call createTable conn -- create a table in the database
|
||
|
call fillTable conn -- enter records to the table
|
||
|
call showTable conn -- show presently stored records
|
||
|
call deleteAndShow conn -- delete a record, show results
|
||
|
call updateAndShow conn -- update a few records, show results
|
||
|
|
||
|
conn~close -- close connection to the database
|
||
|
|
||
|
call compressAndRepair dbFileName
|
||
|
|
||
|
::requires 'oleUtils.frm'
|
||
|
|
||
|
::routine createDataBaseFile
|
||
|
use strict arg dbFileName, appAccess
|
||
|
|
||
|
if stream(dbFileName, "c", "query exists")="" then -- database file does not exist
|
||
|
do
|
||
|
-- We have to know if this is Access 2007 or not. The Jet Engine can not
|
||
|
-- read the Access 2007 default file format. So, if this is Access 2007,
|
||
|
-- we force the database to be created Access 2002-2003 format.
|
||
|
|
||
|
-- create the database file
|
||
|
if appAccess~version >= 12 then do
|
||
|
formatConstant = appAccess~getConstant('acNewDatabaseFormatAccess2002')
|
||
|
appAccess~NewCurrentDatabase(dbFileName, formatConstant)
|
||
|
|
||
|
-- With Access 2007, we have to explicitly close the database, or the
|
||
|
-- Jet Engine can not open it. Prior to Access 2007, this step is not
|
||
|
-- needed.
|
||
|
appAccess~closeCurrentDatabase
|
||
|
end
|
||
|
else do
|
||
|
-- Prior to Access 2007, the NewCurrentDatabase only takes 1 argument.
|
||
|
appAccess~NewCurrentDatabase(dbFileName)
|
||
|
end
|
||
|
say "created database file" pp(dbFileName)
|
||
|
end
|
||
|
else
|
||
|
say "database file" pp(dbFileName) "exists already"
|
||
|
|
||
|
say center(" end of createDataBaseFile ", 70, "-")
|
||
|
say
|
||
|
|
||
|
-- This is the last place in this example where the Access application object
|
||
|
-- is needed. So we close it now. All the work is actually done by the Jet
|
||
|
-- Engine.
|
||
|
appAccess~quit
|
||
|
|
||
|
|
||
|
::routine dropTable
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
signal on any -- intercept any exception
|
||
|
conn~execute("drop table myTable") -- execute SQL statement
|
||
|
say "dropped 'myTable'"
|
||
|
say center(" end of dropTable ", 70, "-")
|
||
|
say
|
||
|
return
|
||
|
any:
|
||
|
say "could not drop 'myTable'"
|
||
|
say condition("C") condition("D")
|
||
|
say center(" end of dropTable ", 70, "-")
|
||
|
say
|
||
|
return
|
||
|
|
||
|
::routine createTable
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
sql="create table myTable (id integer, name text (30) )"
|
||
|
say "executing" pp(sql) "..."
|
||
|
conn~execute(sql)
|
||
|
say center(" end of createTable ", 70, "-")
|
||
|
say
|
||
|
|
||
|
::routine fillTable
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
-- define a few names to insert into the database
|
||
|
s=.set~of("Chip Davis", "Christian Michel", "David Ashley", -
|
||
|
"Florian Große-Coosmann", "Frank Clark", "Gil Barmwater", -
|
||
|
"Jan Engehausen", "Lavrentios Servissoglou", "Lee Peedin", -
|
||
|
"Manfred Schweizer", "Mark Hessling", "Pam Taylor", -
|
||
|
"Reiner Micke", "René Jansen", "Rick McGuire", -
|
||
|
"Rony G. Flatscher", "Stefan Dörsam", "Uwe Berger", -
|
||
|
"Walter Pachl", "Mark Miesfeld" -
|
||
|
)
|
||
|
|
||
|
.local~tmp.nrRecs=s~items -- save number of records in local environment
|
||
|
.local~tmp.length=length(.tmp.nrRecs) -- save length
|
||
|
|
||
|
say "inserting" pp(.tmp.nrRecs) "record(s) into the table..."
|
||
|
say
|
||
|
|
||
|
i=0
|
||
|
do name over s -- iterate over collected items (arbitrary order)
|
||
|
i=i+1 -- calc unique id value
|
||
|
sql="insert into myTable (id, name) values ("i~right(.tmp.length)", '"name"' )"
|
||
|
say " " i~right(.tmp.length)":" pp(sql) -- show sql statement
|
||
|
conn~execute(sql) -- execute the statement
|
||
|
end
|
||
|
say center(" end of fillTable ", 70, "-")
|
||
|
say
|
||
|
|
||
|
|
||
|
::routine showTable
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
rs=.oleObject~new("ADODB.Recordset") -- create an ADO recordset object
|
||
|
|
||
|
rs~CursorType = 1 -- .ole.const~adOpenKeyset
|
||
|
rs~LockType = 3 -- .ole.const~adLockOptimistic
|
||
|
sql="select * from myTable order by id"
|
||
|
rs~open(sql, conn) -- open the table
|
||
|
totRecs=rs~recordCount -- get recordCount
|
||
|
say "nr. of records:" pp(totRecs)":"
|
||
|
say
|
||
|
rs~moveFirst -- just make sure, it is pointing to the first record
|
||
|
do i=1 to totRecs while rs~eof=.false -- loop over record set
|
||
|
say " " i~right(.tmp.length)":" -
|
||
|
"id="pp(rs~fields["id"]~value~right(.tmp.length)) -
|
||
|
"name="pp(rs~fields["name"]~value)
|
||
|
rs~moveNext
|
||
|
end
|
||
|
rs~close
|
||
|
say center(" end of showTable ", 70, "-")
|
||
|
say
|
||
|
|
||
|
::routine deleteAndShow
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
say "deleting a record from the table:"
|
||
|
say
|
||
|
-- delete row with an arbitrary value for the field "id"
|
||
|
sql="delete from myTable where id="random(1,.tmp.nrRecs)
|
||
|
say " executing" pp(sql) "..."
|
||
|
conn~execute(sql)
|
||
|
say
|
||
|
|
||
|
.local~tmp.nrRecs=.tmp.nrRecs-1 -- subtract 1 from total number of records
|
||
|
.local~tmp.length=length(.tmp.nrRecs) -- save length
|
||
|
|
||
|
call showTable conn -- show presently stored records
|
||
|
|
||
|
::routine updateAndShow
|
||
|
use arg conn -- retrieve connection to database
|
||
|
|
||
|
say "updating a few records from the table:"
|
||
|
say
|
||
|
-- update some records randomly
|
||
|
sql="update myTable set id=id*3 where id >" random(1,.tmp.nrRecs)
|
||
|
say " executing" pp(sql) "..."
|
||
|
param = .OLEVariant~new(count)
|
||
|
conn~execute(sql, param)
|
||
|
say " " pp(param~!varValue_) "record(s) affected."
|
||
|
say
|
||
|
|
||
|
call showTable conn -- show presently stored records
|
||
|
|
||
|
|
||
|
::routine compressAndRepair -- Jet (= MS Access) databases tend to grow considerably
|
||
|
parse arg dbFileName
|
||
|
|
||
|
say "before compress & repair, database file size:" pp(stream(dbFileName, "c", "query size"))
|
||
|
compressedName=SysTempFileName(dbFileName"-cmp???") -- get a unique new file name
|
||
|
|
||
|
/*
|
||
|
-- Hint: if your MDB-database is secured with a database password, you must supply
|
||
|
-- that password in the variable "pw":
|
||
|
|
||
|
if pw="" then pwdString=""
|
||
|
else pwdString=";Jet OLEDB:Database Password="pw
|
||
|
|
||
|
fromString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="dbFileName || pwdString
|
||
|
toString ="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="compressedName || pwdString
|
||
|
*/
|
||
|
|
||
|
-- Hint: MDB database files have no database password set
|
||
|
fromString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="dbFileName
|
||
|
toString ="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="compressedName
|
||
|
|
||
|
|
||
|
jro=.oleObject~new("JRO.JetEngine")
|
||
|
jro~compactDatabase(fromString, toString) -- carry out compact & repair
|
||
|
say "after compress & repair, database file size:" pp(stream(compressedName, "c", "query size"))
|
||
|
|
||
|
-- create a unique backup file name containing date and time of backup
|
||
|
newBkpFileName=dbFileName"-bkp-"date("S")"-"time()~changestr(":","")
|
||
|
|
||
|
say "renaming work files..."
|
||
|
-- rename original database file name to backup file name
|
||
|
cmdText="ren" '"'dbFileName'"' filespec("Name", newBkpFileName)
|
||
|
say "DOS command:" pp(cmdText)
|
||
|
address cmd cmdText -- use ADDRESS CMD explicitly
|
||
|
|
||
|
-- now rename new compressed file to original databse file name
|
||
|
cmdText="ren" '"'compressedName'"' filespec("Name", dbFileName)
|
||
|
say "DOS command:" pp(cmdText)
|
||
|
cmdText -- this uses ADDRESS CMD implicitly
|
||
|
say center(" end of compressAndRepair ", 70, "-")
|
||
|
say
|
||
|
|
||
|
|
||
|
::routine pp
|
||
|
return "[" || arg(1) || "]"
|
||
|
|
||
|
::routine checkSystem
|
||
|
|
||
|
mode = getAddressingMode()
|
||
|
if mode == 64 then do
|
||
|
say
|
||
|
say 'You are running a 64-bit compiled ooRexx'
|
||
|
say
|
||
|
say 'This sample relies on the Microsoft Jet Database Engine.'
|
||
|
say 'There is no 64-bit version of Jet, therefore a 64-bit'
|
||
|
say 'compiled ooRexx can not run this sample. This sample'
|
||
|
say 'may be upgraded in the future to work with Access 2007'
|
||
|
say 'and its Microsoft Access Engine (ACE). ACE is also'
|
||
|
say '32-bit. It is not clear at the current time whether ACE'
|
||
|
say 'will be ported to 64-bit.'
|
||
|
say
|
||
|
say 'Sorry, this sample is 32-bit only.'
|
||
|
|
||
|
return .nil
|
||
|
end
|
||
|
|
||
|
-- We have to have Access, exit gracefully if not.
|
||
|
accessApplication = createOleObject("Access.Application", .true)
|
||
|
if accessApplication == .nil then do
|
||
|
say
|
||
|
say 'This example requires Microsoft Access, which does not seem'
|
||
|
say 'to be installed. Quitting.'
|
||
|
end
|
||
|
|
||
|
return accessApplication
|
||
|
|