• Iguana 6
  • Previous Versions
  • API
  • Sample Code
  • Forums
  • Training
  • Create a Ticket
iNTERFACEWARE Help Center
  • Iguana 6
  • Previous Versions
  • API
  • Sample Code
  • Forums
  • Training
  • Create a Ticket

Code Repository

Home›Code Repository›custom_merge.lua
Modules

custom_merge.lua

Verified Featured
Added by iNTERFACEWARE

A customizable database merge method for Iguana 5.5.1 and up.

Source Code
-- $Revision: 1.5 $
-- $Date: 2013-10-23 20:30:15 $

--
-- The custom_merge module
-- Copyright (c) 2012-2013 iNTERFACEWARE Inc. ALL RIGHTS RESERVED
-- iNTERFACEWARE permits you to use, modify, and distribute this file in accordance
-- with the terms of the iNTERFACEWARE license agreement accompanying the software
-- in which it is used.
--

if not db then
   error('db module must be present.', 2)
end

if not db.connect then
   error('db.customMerge requires Iguana 5.5.1 or newer.', 2)
end
----------------------
local Conn, Params --The database handle and parameters for the method call.
local SQLmethods --Database specific methods, defined below.
----------------------
-- Returns a table with object-oriented methods to collect non-nil strings and to return them concatenated.
-- Inspired by: http://snippets.luacode.org/?p=snippets/String_Writer_108 (MIT/X11 License)
-- Due to technical limitations, the variable arguments (...) to write() may not display properly in annotations.

local function getStringWriter()
   return setmetatable({}, {
         __index = {
            write = function(self, ...)
               for _,s in ipairs({...}) do --Nil arguments will cause subsequent ones to be ignored.
                  self[#self + 1] = tostring(s)      
               end
               return self
            end,
            getValue = function(self)
               return table.concat(self)
            end
         },         
         _display = function(self)
            --Used in annotations - short and long view.
            return 'StringWriter', self:getValue()
         end
      }
   )
end
----------------------

local function OracleDateTime(Node, Buffer)
   Buffer:write('TO_DATE(\'', Node, '\', \'YYYY-MM-DD HH24:MI:SS\')')
end

local function addSQLValue(Node, Buffer)
   if Node:isNull() then
      Buffer:write('NULL')
      return
   end
   
   local curType = Node:nodeType()
   
   if SQLmethods[Params.api].AddValue and SQLmethods[Params.api].AddValue[curType] then
      SQLmethods[Params.api].AddValue[curType](Node, Buffer)
   elseif curType == 'string' then
      if tostring(Node):match('0x') then
         Buffer:write(Node)
      else
         Buffer:write(Conn:quote(tostring(Node)))
      end
   elseif curType == 'datetime' then      
      Buffer:write(Conn:quote(tostring(Node)))
   else --Double or integer assumed.
      Buffer:write(Node)
   end   
end

local function addKeyConditions(Row, Buffer, Keys)
   Buffer:write(' WHERE ')
   for i = 1,#Keys do
      if i > 1 then
         Buffer:write(' AND ')
      end
      Buffer:write(Keys[i], ' = ')
      addSQLValue(Row[Keys[i]], Buffer)            
   end   
end

local function addRowInsert(TableName, Row, Buffer)   
   Buffer:write('INSERT INTO ', TableName, '(')
   local NotFirstVal = false
   for i=1, #Row do
      if Row[i]:nodeValue() ~= '' or
         (Row[i]:nodeValue() == '' and Params.merge_null) then
         if i ~= 1 and NotFirstVal then
            Buffer:write(', ')
         end
         Buffer:write(Row[i]:nodeName())         
         NotFirstVal = true
      end
   end   
   Buffer:write(') VALUES(')
   NotFirstVal = false
   for i=1, #Row do
      if Row[i]:nodeValue() ~= '' or
         (Row[i]:nodeValue() == '' and Params.merge_null) then
         if NotFirstVal then
            Buffer:write(', ')
         end
         addSQLValue(Row[i], Buffer)
         NotFirstVal = true
      end
   end
   Buffer:write(')')
end

local function addRowUpdate(TableName, Row, Buffer, Keys)   
   Buffer:write('UPDATE ', TableName, ' SET ')
   local NotFirstVal = false
   for i=1, #Row do      
      if Row[i]:nodeValue() ~= '' or
         (Row[i]:nodeValue() == '' and Params.merge_null) then
         if NotFirstVal then
            Buffer:write(', ')
         end
         Buffer:write(Row[i]:nodeName(),' = ')
         addSQLValue(Row[i], Buffer)
         NotFirstVal = true
      end
   end   
   addKeyConditions(Row, Buffer, Keys)    
end

local function genericMergeSQL(Table, Statements, Keys)
   for i=1, #Table do
      local Buffer = getStringWriter()
      if i > 1 then
         Buffer:write('\n')
      end
      local count = 0
      if #Keys > 0 then
         local CountBuffer = getStringWriter()
         CountBuffer:write('SELECT COUNT(', Keys[1],
            ') FROM ', Table:nodeName())
         addKeyConditions(Table[i], CountBuffer, Keys)            
         
         local results = Conn:query(CountBuffer:getValue())
         count = tonumber(results[1][1]:nodeValue())
      end
      if count == 0 then
         addRowInsert(Table:nodeName(), Table[i], Buffer)
      elseif count == 1 then
         addRowUpdate(Table:nodeName(), Table[i], Buffer, Keys)         
      else         
         error('Multiple rows found with same primary keys in table '
            .. Table:nodeName() .. '.')
      end
      Statements[#Statements+1] = Buffer:getValue()
   end
end

local function batchBeginEnd(Statements)
   return 'BEGIN\n' .. table.concat(Statements, ';\n') .. ';\nEND;'
end

SQLmethods = { --Declared as local at the top.
   [db.MY_SQL]      = {Merge = genericMergeSQL},
   [db.ORACLE_OCI]  = {Merge = genericMergeSQL, Batch = batchBeginEnd, AddValue = {datetime = OracleDateTime}},
   [db.ORACLE_ODBC] = {Merge = genericMergeSQL, Batch = batchBeginEnd, AddValue = {datetime = OracleDateTime}},
   [db.SQL_SERVER]  = {Merge = genericMergeSQL, Batch = batchBeginEnd},
   [db.SQLITE]      = {Merge = genericMergeSQL},
}

local function getMergeStatements()
   local Tables = Params.data      
  
   local SQLstatements = {}   
   for i=1, #Tables do      
      --Need non-zero number of rows to merge and to determine keys.
      if #Tables[i] > 0 then
         local Keys = {}      
         
         for j=1,#Tables[i][1] do
            if Tables[i][1][j]:isKey() then
               Keys[#Keys + 1] = Tables[i][1][j]:nodeName()
            end
         end      
         
         SQLmethods[Params.api].Merge(Tables[i], SQLstatements, Keys)
      end 
   end
   return SQLstatements
end

local function checkTable(T, CallDepth)
   if type(T) ~= 'table' then
      error('Expected a table of parameters.', CallDepth)
   end
end

local function checkParam(ParamTable, List, CallDepth)
   for i=1, #List do
      if not ParamTable[List[i]] then
         error('Missing parameter "'..List[i]..'".', CallDepth)
      end
   end
end

local function getParams(arg1, arg2, CallDepth)
   if getmetatable(arg1) == getmetatable(db)._submeta.db_connection then
      --First argument is a db.connection, so second one is the method parameters.
      Conn, Params = arg1, arg2
      
      Params.api = Conn:info().api
      Params.name = Conn:info().name
      
      if Conn:info().live == false --Connection itself isn't live.
         then Params.live = false
      end
   else --Assume no db.connection was passed in.  
      Params = arg1
      checkTable(Params, CallDepth + 1)
      checkParam(Params, {'api','name','user','password'}, CallDepth + 1)
      Conn = db.connect{ 
         api=Params.api, 
         name=Params.name,
         user =Params.user,
         password = Params.password,
         use_unicode = Params.use_unicode,
         timeout = Params.timeout,
         live = Params.live
      }
      Params.IsTempConnection = true
   end   
   
   checkParam(Params, {'data'}, CallDepth + 1)
      
   if not SQLmethods[Params.api] then 
      error('Merge using this database API is not currently supported.', CallDepth)
   end
      
   if type(Params.data) ~= 'userdata' or not Params.data:nodeType() or Params.data:nodeType() ~= 'table_collection' then
      error('The data parameter must be a table collection as produced from db.tables{}.', CallDepth)
   end 
   
   -- merge_null defaults to true to match db.merge() behaviour
   if Params.merge_null == nil then Params.merge_null = true end
      
   -- transaction defaults to true to match db.merge() behaviour
   if Params.transaction == nil then Params.transaction = true end
end

function db.customMerge(arg1, arg2, CallDepth)
   if not CallDepth then
      --Assume this function is being called directly.
      CallDepth = 2
   end
   
   getParams(arg1,arg2,CallDepth + 1)
   
   local MergeSQL = getMergeStatements()     
   
   if Params.transaction then            
      Conn:begin{live = Params.live}      
   end
      
   local success, result = pcall(
      --Enclosing the merge in a pcall to be able to roll back.
      function(Conn, Params, MergeSQL)
         if SQLmethods[Params.api].Batch then --Batch the operations if we can.
            Conn:execute{sql = SQLmethods[Params.api].Batch(MergeSQL), live = Params.live}
         else
            for i=1,#MergeSQL do
               Conn:execute{sql = MergeSQL[i], live = Params.live}
            end
         end
      end,
      Conn, Params, MergeSQL
   )
   
   --Commit or rollback the transaction, if we began one.
   if success then 
      if Params.transaction then Conn:commit{live = Params.live} end
   else
      if Params.transaction then Conn:rollback{live = Params.live} end
      error(result, CallDepth)
   end
   
   if Params.IsTempConnection then Conn:close() end
      
   if Params.live ~= true then
      return 'Operation not live.'
   end           
end

--A wrapper for db.customMerge for object-oriented use with db.connections.
--We do it this way to attach different help data to the wrapper.
getmetatable(db)._submeta.db_connection.__index.customMerge = function(arg1, arg2)   
   local CallDepth = 2 --Used for bubbling up errors to where the method is being called.
   checkTable(arg2, CallDepth + 1) --Expecting a db.connection and a table of parameters.     
   local res = db.customMerge(arg1, arg2, CallDepth + 1)
   --Avoiding a tail call (which changes the call stack in Lua) so errors can be bubbled up properly.
   return res
end

if help then
   --Help data for use as db.customMerge().
   help.set{input_function=db.customMerge,      
      help_data= {
         SummaryLine = 'Merges records into a database (customizable Lua method).',
         Desc = 'Merges records into a database. This is a special, customizable method written in Lua. '..
         'It currently supports MySQL, SQL Server, Oracle ODBC/OCI, and SQLite.',
         Parameters = {
            {api = {Desc = 'Database API, should be in the form: db.SQL_SERVER, db.MY_SQL, etc.'}},
            {name = {Desc = 'Database name/address.'}},
            {user = {Desc = 'User name.'}},
            {password = {Desc = 'Password.'}},         
            {data = {Desc = 'The data to merge, in the form of a node tree created using db.tables().'}},         
            {live = {Desc = 'Whether operation should be executed live in the editor (default = false).', Opt = true}},
            {use_unicode = {Desc = 'Whether Unicode should be used when communicating with the database.', Opt = true}},
            {timeout = {Desc = 'Maximum time in seconds allowed for the connection (0 for infinite).', Opt = true}},
            {transaction = {Desc = 'Whether merge should be performed as a transaction (default = true).', Opt = true}},
            {merge_null = {Desc = 'Whether NULL values should be updated/inserted (default = true).', Opt = true}},                 
         },         
         Title = 'db.customMerge',
         Usage = "db.customMerge{api=<value>, name=<value>, ...}",
         SeeAlso = {{Title = 'More on customMerge', Link = 'http://wiki.interfaceware.com/888.html'}},
         ParameterTable = true
      }
   } 
   
   --Help data for use as conn:customMerge().
   help.set{input_function=getmetatable(db)._submeta.db_connection.__index.customMerge,
      help_data={
         SummaryLine = 'Merges records into a database (customizable Lua method).',
         Desc = 'Merges records into a database. This is a special, customizable method written in Lua. '..
         'It currently supports MySQL, SQL Server, Oracle ODBC/OCI, and SQLite.',
         Parameters = {      
            {data = {Desc = 'The data to merge, in the form of a node tree created using db.tables().'}},         
            {live = {Desc = 'Whether operation should be executed live in the editor (default = false).', Opt = true}},
            {transaction = {Desc = 'Whether merge should be performed as a transaction (default = true).', Opt = true}},         
            {merge_null = {Desc = 'Whether NULL values should be updated/inserted (default = true).', Opt = true}},                      
         },         
         Title = 'customMerge',
         Usage = "Conn:customMerge{data=<value> [, merge_null=<value>] [, ...]}",
         SeeAlso = {{Title = 'More on customMerge', Link = 'http://wiki.interfaceware.com/888.html'}},
         ParameterTable = true
      }
   }
end
Description
A customizable database merge method for Iguana 5.5.1 and up.
Usage Details

At its core customMerge() does a SELECT call on the target table using its Primary Key(s). If a matching row is found it is updated, if no row is found a new row is inserted. This algorithm uses the same logic as the built-in conn:merge{} function, with one important difference: The addition of the merge_null parameter, which controls whether NULLs are written to the database.

How to use custom_merge.lua:

  • Add it to your shared modules in any Translator project.
  • Make sure to require 'custom_merge' at the top of your script.
  • Use the Translator auto-completion to understand the parameters.
  • Call the customMerge() method anywhere you would call the regular merge, either on database connection objects as conn:customMerge() or by itself as db.customMerge().
  • Dive into the customMerge() code using the Translator annotations to help you make the changes you need.
More Information
• A customizable Lua database merge method
• Forum thread with foreign key support added by a customer
• Customize custom_merge.lua for table and field names with spaces
• A customizable Lua database merge method
Bookmark
  • Reviews
  • Related Listings
Filter
Sort by: Newest First
  • Oldest First
  • Rating
  • Helpfulness
Write a Review
Rating
Keyword
Filter
Sort by: Title
  • Newest First
  • Oldest First
  • Most Reviews
  • Highest Rated
Rating
iNTERFACEWARE
sha1.lua
Added by iNTERFACEWARE
Modules
A pure Lua-based implementation of the popular SHA-1 hashing function.
sqlite.lua
Added by iNTERFACEWARE
Modules
A module to create a SQLite database and generate the tables specified in a VMD
store.lua
Added by iNTERFACEWARE
Modules
The "original" store module: Allows you to store key/value pairs in a persistent storage mechanism. We recommend using the new store2 module instead.
store2.lua
Added by iNTERFACEWARE
Modules
Provides a simple interface to store key/value pairs in a persistent storage mechanism.
stream.lua
Added by iNTERFACEWARE
Modules
This module performs basic stream processing, read, write, save to file, convert to text etc.
stringutil.lua
Added by iNTERFACEWARE
Modules
A library of helpful extensions to the standard Lua string library.
test_all.lua
Added by iNTERFACEWARE
Modules
Test a script against all the sample messages loaded for the component
throttle.lua
Added by iNTERFACEWARE
Modules
Throttle a process during peak hours, by slowing down the code.
throttleDB.lua
Added by iNTERFACEWARE
Modules
Throttle database access by reducing the number of inserts during peak hours
urlcode.lua
Added by iNTERFACEWARE
Modules
A module for parsing URL encoded GET/POST sequences
validate.lua
Added by iNTERFACEWARE
Modules
A template module for testing HL7 message conformance, you will need to extend it to match your requirements
xml.lua
Added by iNTERFACEWARE
Modules
A collection of helpful XML node functions.
Showing 21 - 32 of 32 results
«12»

Topics

  • expandGetting Started
  • expandAdministration
    • expandInstallation
    • expandLicensing
    • expandUpgrades
    • expandDeployment
    • expandConfiguration Management
      • expandCustom Configuration
    • expandBackup and Restore
    • expandSecurity
      • expandHIPAA Compliance
    • expandTroubleshooting
  • expandDeveloping Interfaces
    • expandArchitecture
    • expandInterfaces
      • expandHL7
      • expandDatabase
        • expandConnect
      • expandWeb Services
      • expandCDA
      • expandX12
      • expandOther Interfaces
      • expandUtilities
    • expandRepositories
      • expandBuiltin Repositories
        • expandIguana Upgrade
        • expandIguana Tutorials
        • expandIguana Tools
        • expandIguana Protocols
        • expandIguana Files
        • expandIguana Date/Time
        • expandIguana Webservices
        • expandIguana Excel
      • expandRemote Repositories
      • expandCS Team Repositories
        • expandIguana Channels
    • expandSample Code
      • expandModules
      • expandUsing built-in functions
      • expandWorking with XML
    • expandLua Programming
    • expandPerformance
  • expandFAQs and TIPs
    • expandFrequently Asked Questions
      • expandInstalls and Upgrades
      • expandWeb Services
      • expandConfiguration
      • expandChannels
      • expandTranslator
      • expandOther
      • expandDatabase
      • expandAdministration
      • expandLogs
      • expandChameleon
    • expandTips
      • expandChannels
      • expandChameleon
      • expandWeb Services
      • expandSecurity
      • expandProgramming
      • expandOther
      • expandAdministration
  • expandReference
    • expandIguana Enterprise and Professional
    • expandProgram Settings
    • expandChannel Settings
    • expandDashboard
    • expandChannels
    • expandTranslator
    • expandLogs
      • expandLog Encryption
    • expandHTTP API
    • expandCDA API
    • expandError Messages
    • expandChameleon
    • expandIguana Change Log

Other Links

  • Training Center
  • News & Announcements
  • iNTERFACEWARE Blog
  • Older Documention (IGUANA v4 & Chameleon)
Copyright © iNTERFACEWARE Inc.