Data Management

Python & Btrieve 2 on Windows: Accessing Zen Data With NoSQL

Actian Corporation

February 2, 2018

Connecting ServiceNow to other applications' data doesn’t have to be difficult

In my previous blog (“Programming the Easy Way: Accessing a PSQL Zen Database with Python and ODBC“), I showed how to easily access an Actian Zen database with Python and ODBC. My next project was to use the new Btrieve 2 feature with Python to directly interface with the data files without using any SQL.

Long-time Btrieve/PSQL/Zen customers are familiar with the old Btrieve function, a classic 3GL programming interface that hasn’t really changed in over 30 years. It provides direct access to the engine so that you can manipulate data files with options like Create, Open, Insert, Update, Delete, GetFirst, GetNext, GetEqual, etc. Now, with Actian Zen v13 we have Btrieve 2, which offers a simplified, more intuitive object-oriented interface covering the full set of Btrieve calls to the database engine. This interface is provided for C/C++ developers, but we also supply SWIG (Simplified Wrapper & Interface Generator) files so the interface can be used by Python, Perl, and PHP developers.

Getting setup to use Btrieve 2 with Python requires a few steps:

  1. Prepare your Windows environment:
    • Install the Actian Zen v13 database engine
    • Install Python for Windows – I used v3.6 64-bit from python.org
    • Download & unzip Swig for Windows – I used Swigwin-3.0.12 from swig.org
    • Download & unzip the Btrieve 2 SDK
    • Visual Studio 2015 or later is also required; you can install a Community Version from Microsoft if you don’t already have one from here.
  2. Create a MyPrograms folder for your Python code, and copy in the following files from the Btrieve 2 SDK:
    • btrieveCpp.lib & btrieveC.lib from win64
    • btrieveC.h & btrieveCpp.h from include
    • btrievePython.swig & btrieveSwig.swig from swig
  3. Open a command prompt and change to your MyPrograms folder, and enter the following command:
    <swigwin_location>swig -c++ -D_WIN64 -python btrievePython.swig
    This creates the following files:  btrievePython.py  &  btrievePython_wrap.cxx
  4. Create a text file in MyPrograms called setup.py containing the following:
    from distutils.core import setup, Extension
    btrievePython_module = Extension('_btrievePython', sources=['btrievePython_wrap.cxx'],
         library_dirs=['<MyPrograms location>'], libraries=['btrieveCpp'],  )
    setup (name='btrievePython', version='1.0', author='Actian',
         description="""Compile Btrieve 2 Python module""",
         ext_modules=[btrievePython_module], py_modules=["btrievePython"], )
  5. In a command prompt, in the MyPrograms folder, run this command to build the btrievePython module:
    python setup.py build_ext --plat-name="win-amd64"
    This creates the compiled Python file buildlib.win-amd64-3.6_btrievePython.cp36-win_amd64.pyd
  6. Rename the .pyd file to just _btrievePython.pyd and copy it to MyPrograms or the DLLs folder under your Python installation.

Once these steps are completed, you should be able to “import btrievePython” in a Python environment or program.

Once the btrievePython import library is setup, you are ready to write Btrieve 2 applications in Python!  Here’s an example that performs the same database operations as the ODBC version from my previous blog – create the same file, insert records, and retrieve the total count inserted.  This code is about twice as long as the ODBC version, but it accesses the file directly without going through the SQL layer.   (Note – only minimal error checking has been included):

import os
import sys
import struct
import btrievePython as btrv

btrieveFileName = "Test_Table.mkd"
recordFormat = "<iB32sBBBH"
recordLength = 42
keyFormat = "<i"

# Create a session:
btrieveClient = btrv.BtrieveClient(0x4232, 0) # ServiceAgent=B2

# Specify FileAttributes for the new file:
btrieveFileAttributes = btrv.BtrieveFileAttributes()
rc = btrieveFileAttributes.SetFixedRecordLength(recordLength)
# Specify Key 0 as an autoinc:
btrieveKeySegment = btrv.BtrieveKeySegment()
rc = btrieveKeySegment.SetField(0, 4, btrv.Btrieve.DATA_TYPE_AUTOINCREMENT)
btrieveIndexAttributes = btrv.BtrieveIndexAttributes()
rc = btrieveIndexAttributes.AddKeySegment(btrieveKeySegment)
rc = btrieveIndexAttributes.SetDuplicateMode(False)
rc = btrieveIndexAttributes.SetModifiable(True)

# Create the file:
rc = btrieveClient.FileCreate(btrieveFileAttributes, btrieveIndexAttributes,
btrieveFileName, btrv.Btrieve.CREATE_MODE_OVERWRITE)
if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR):
     print('nFile "' + btrieveFileName + '" created successfully!')
else:
     print('nFile "' + btrieveFileName + '" not created; error: ', rc)

# Allocate a file object:
btrieveFile = btrv.BtrieveFile()
# Open the file:
rc = btrieveClient.FileOpen(btrieveFile, btrieveFileName, None, btrv.Btrieve.OPEN_MODE_NORMAL)
if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR):
     print('File open successful!n')
else:
     print('File open failed - status: ', rc, 'n')

# Insert records:
iinserting = True
while iinserting:
     new_name = input('Insert name (Q to quit): ' )
     if new_name.lower() == 'q':
          iinserting = False
     else:
          record = struct.pack(recordFormat, 0, 0, new_name.ljust(32).encode('UTF-8'), 0, 22, 1, 2018)
          rc = btrieveFile.RecordCreate(record)
          if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR):
               print(' Insert successful!')
          else:
               print(' Insert failed - status: ', rc)

# Get Record count:
btrieveFileInfo = btrv.BtrieveFileInformation()
rc = btrv.BtrieveFile.GetInformation(btrieveFile, btrieveFileInfo)
print('nTotal Records inserted =', btrieveFileInfo.GetRecordCount())

# Close the file:
rc = btrieveClient.FileClose(btrieveFile)
if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR):
     print('File closed successful!')
else:
     print('File close failed - status: ', rc)

Since the above example doesn’t actually do any data reads, I went back and added a little more code before the closing the file to demonstrate a file scan that looks for a name:

# Look up record by name
ireading = True
while ireading:
     find_name = input('nFind name (Q to quit): ' )
     if find_name.lower() == 'q':
          ireading = False
     else:
          foundOne = False
          record = struct.pack(recordFormat, 0, 0, ' '.ljust(32).encode('UTF-8'), 0, 0, 0, 0)
          readLength = btrieveFile.RecordRetrieveFirst(btrv.Btrieve.INDEX_NONE, record, 0)
          while (readLength > 0):
               recordUnpacked = struct.unpack(recordFormat, record)
               if (recordUnpacked[2] == find_name.ljust(32).encode('UTF-8')):
                    print(' Matching record found: ID:', recordUnpacked[0], ' Name:', recordUnpacked[2].decode())
                    foundOne = True
               readLength = btrieveFile.RecordRetrieveNext(record, 0)
          if (foundOne == False):
               print(' No record found matching "'+find_name+'"')
          status = btrieveFile.GetLastStatusCode()
          if (status != btrv.Btrieve.STATUS_CODE_END_OF_FILE):
               print(' Read error: ', status, btrv.Btrieve.StatusCodeToString(status))

The simplicity of Python programming combined with the new Btrieve 2 development interface will allow for the fast turn-around of new Zen applications!

If you have any questions about Zen or other Actian products please feel free to ask in our community forums.

actian avatar logo

About Actian Corporation

Actian makes data easy. We deliver cloud, hybrid, and on-premises data solutions that simplify how people connect, manage, and analyze data. We transform business by enabling customers to make confident, data-driven decisions that accelerate their organization’s growth. Our data platform integrates seamlessly, performs reliably, and delivers at industry-leading speeds.