• Iguana 6
  • Previous Versions
  • API
  • Sample Code
  • Training
  • Create a Ticket
iNTERFACEWARE Help Center
  • Iguana 6
  • Previous Versions
  • API
  • Sample Code
  • 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
age.lua
Added by iNTERFACEWARE
Modules
This module calculates age from DOB, it returns years, months and partial years (i.e., 17, 3, 17.296272)
auth.lua
Added by iNTERFACEWARE
Modules
A module that does basic authentication for incoming web requests
batch.lua
Added by iNTERFACEWARE
Modules
A module to help processing batched HL7 messages
codemap.lua
Added by iNTERFACEWARE
Modules
This module is used to map one set of codes to another set of codes, or to validate code membership in a set
csv_parse.lua
Added by iNTERFACEWARE
Modules
A module for parsing well-formed CSV files.
dateparse.lua
Added by iNTERFACEWARE
Modules
A fuzzy date/time parser that is very useful for automatically translating a wide variety of date/time formats.
dup.lua
Added by iNTERFACEWARE
Modules
Duplicate message filter.
edifact.lua
Added by iNTERFACEWARE
Modules
Convert EDI messages to HL7 format so you can process them like an HL7 message (and convert them back to EDI afterwards)
hl7.findSegment.lua
Added by iNTERFACEWARE
Modules
A utility for finding any HL7 segment in a parsed HL7 message node tree.
hl7.serialize.lua
Added by iNTERFACEWARE
Modules
Serializes an HL7 message using specified non-standard delimiters and/or escape characters
hl7.zsegment.lua
Added by iNTERFACEWARE
Modules
Generic Z segment parser. Parses Z segments without needing grammar definitions in the VMD file.
iguanaServer.lua
Added by iNTERFACEWARE
Modules
Provides programmatic access to various operations that can be performed on Iguana channels.
llp.lua
Added by iNTERFACEWARE
Modules
Allows you to use LLP connections from a Translator script
mime.lua
Added by iNTERFACEWARE
Modules
Sends MIME-encoded email attachments using the SMTP protocol. A wrapper around net.smtp.send.
resubmit.lua
Added by iNTERFACEWARE
Modules
Resubmit a logged message to an Iguana channel using the unique reference number (refmsgid).
retry.lua
Added by iNTERFACEWARE
Modules
A module for retrying operations which might periodically fail like database operations.
rtf.lua
Added by iNTERFACEWARE
Modules
A module for converting a RTF file to plain text.
scheduler.lua
Added by iNTERFACEWARE
Modules
Schedule jobs to run at a specified time of day, very useful for batch processing
scrub.lua
Added by iNTERFACEWARE
Modules
The “scrub” module given below redacts sensitive information from HL7 messages.
sha1.lua
Added by iNTERFACEWARE
Modules
A pure Lua-based implementation of the popular SHA-1 hashing function.
Showing 1 - 20 of 31 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.