In this article Barry Mavin, CEO and Chief Software Architect for Recital, details how to use the Recital Database Server with Visual FoxPro.
Recital is a rich and versatile product with many ways to do the same thing. Developers usually write code in the way that they are accustomed to without paying much attention to how this will perform in a multi-user environment with large amounts of users and transactions. The best way to optimize Recital applications is to use the built-in tuning capabilities introduced in Recital 10.
A quick tip for optimizing TCP performance on linux.
edit /etc/sysctl.conf add the lines:
If using gigabit ethernet:
net.ipv4.tcp_mem= 98304 131072 196608
net.ipv4.tcp_window_scaling=1
net.core.wmem_default = 65536
net.core.rmem_default = 65536
net.core.wmem_max=8388608
To reload these use:
# sysctl -p
If using infiniband:
net.ipv4.tcp_window_scaling=1
net.ipv4.tcp_timestamps=0
net.ipv4.tcp_sack=0
net.ipv4.tcp_rmem=10000000 10000000 10000000
net.ipv4.tcp_wmem=10000000 10000000 10000000
net.ipv4.tcp_mem=10000000 10000000 10000000
net.core.rmem_max=524287
net.core.wmem_max=524287
net.core.rmem_default=524287
net.core.wmem_default=524287
net.core.optmem_max=524287
net.core.netdev_max_backlog=300000
After split brain has been detected, one node will always have the resource in a StandAlone connection state. The other might either also be in the StandAlone state (if both nodes detected the split brain simultaneously), or in WFConnection (if the peer tore down the connection before the other node had a chance to detect split brain).
At this point, unless you configured DRBD to automatically recover from split brain, you must manually intervene by selecting one node whose modifications will be discarded (this node is referred to as the split brain victim). This intervention is made with the following commands:
# drbdadm secondary resource
# drbdadm disconnect resource
# drbdadm -- --discard-my-data connect resource
On the other node (the split brain survivor), if its connection state is also StandAlone, you would enter:
# drbdadm connect resource
You may omit this step if the node is already in the WFConnection state; it will then reconnect automatically.
If all else fails and the machines are still in a split-brain condition then on the secondary (backup) machine issue:
drbdadm invalidate resource
Recital is a proven and cost-effective database solution that will help reduce the cost of your database and application software infrastructure substantially. As an added benefit, Recital can run many legacy applications with little to no change as it understands FoxBASE, FoxPRO and Clipper languages as a subset of it's overall capability.
Binary distributions for Unison can be found here.
The user manual can be found here.
We are pleased to announce the release of Recital 10.0.3.
Here is a brief list of features and functionality that you will find in the 10.0.3 release.
- New Commands:
- SET TMPNAMPATH ON|OFF
- REMOVE TABLE - New Functions:
- CURSORGETPROP()
- CURSORSETPROP()
- CURVAL()
- GETFLDSTATE()
- OLDVAL()
- TABLEREVERT()
- TABLEUPDATE()
- SETFLDSTATE() - Enhanced Functions:
- TMPNAM() - additional parameter to specify the return of basename only
- MAILATTACH() - parameter changed from array to filename to allow directory and file extension to be specified - Enhancements:
- DO level increased from 32 to 64. - Fixes:
- Delay exiting Recital after SYS(3) or SYS(2015)
- SET SOFTSEEK issue when search key above first record in index
- Compilation error with REPLACE command after UDF call
- FETCH INTO memvars error
- END TRANSACTION at command prompt error
- ROLLBACK locking error
- Linux ODBC Driver undefined symbol error
- RELEASE variable with same name as variable in calling program issue
- SQLCODE() issue on non-gateway data access
- Issuing two SQLEXEC() calls error
- LASTSEQNO() in workareas > 1 error
- SET RELATION to detail table in workarea 1 issue
- LIST STATUS on empty table delay
- SET AUTOCATALOG alias entries error
- ADD OBJECT in DEFINE CLASS error
- DEACTIVATE WINDOW error
- SORT error
- Other reported bugs
In this article Barry Mavin, CEO and Chief Software Architect for Recital details how to Build C Extension Libraries to use with Recital.
Overview
It is possible to extend the functionaliy of Recital products using "Extension libraries" that can be written in C. These extension libraries, written using the Recital/SDK API, are dynamically loadable from all Recital 9 products. This includes:
- Recital
- Recital Server
- Recital Web
Building C Extension Libraries
You can create C wrappers for virtually any native operating system function and access these from the Recital 4GL. Unlike traditional APIs which only handle the development of C functions that are callable from the 4GL, the Recital/SDK allows you to build Classes that are accessible from all Recital products. e.g. You could create a GUI framework for Linux that handles VFP system classes!
To deploy your C Extension Libraries, copy them to the following location:
Windows:
\Program Files\Recital\extensions
Linux/Unix:
/opt/recital/extensions
Please see the Recital/SDK API Reference documentation for further details.
Sample code
Listed below is the complete example of a C Extension Library.:
//////////////////////////////////////////////////////////////////////////////// #include "mirage_demo.h" //////////////////////////////////////////////////////////////////////////////// // Declare your functions and classes below as follows: // // Recital Function Name, C Function Name, Type (Function or Class) // #define MAX_ELEMENTS 7 static struct API_SHARED_FUNCTION_TABLE api_function_table[MAX_ELEMENTS] = { {"schar", "fnSamplesCharacter", API_FUNCTION}, {"stype", "fnSamplesType", API_FUNCTION}, {"slog", "fnSamplesLogical", API_FUNCTION}, {"snum", "fnSamplesNumeric", API_FUNCTION}, {"sopen", "fnSamplesOpen", API_FUNCTION}, {"myclass", "clsMyClass", API_CLASS}, {NULL, NULL, -1} }; //////////////////////////////////////////////////////////////////////////////// // Recital API initialization. This should be in only ONE of your C files // **IT SHOULD NEVER BE EDITED OR REMOVED** INIT_API; /////////////////////////////////////////////////////////////////////// // This is an example of passing a character parameter and returning one. RECITAL_FUNCTION fnSamplesCharacter(void) { char *arg1; if (!_parse_parameters(PCOUNT, "C", &arg1)) { ERROR(-1, "Incorrect parameters"); } _retc(arg1); } /////////////////////////////////////////////////////////////////////// // This is an example of passing a numeric parameter and returning one. RECITAL_FUNCTION fnSamplesNumeric(void) { int arg1; if (!_parse_parameters(PCOUNT, "N", &arg1)) { ERROR(-1, "Incorrect parameters"); } _retni(arg1); } /////////////////////////////////////////////////////////////////////// // This is an example returns the data type of the parameter passed. RECITAL_FUNCTION fnSamplesType(void) { char result[10]; if (PCOUNT != 1) { ERROR(-1, "Incorrect parameters"); } switch (_parinfo(1)) { case API_CTYPE: strcpy(result, "Character"); break; case API_NTYPE: strcpy(result, "Numeric"); break; case API_LTYPE: strcpy(result, "Logical"); break; case API_DTYPE: strcpy(result, "Date"); break; case API_TTYPE: strcpy(result, "DateTime"); break; case API_YTYPE: strcpy(result, "Currency"); break; case API_ATYPE: strcpy(result, "Array"); break; default: strcpy(result, "Unkown"); break; } _retc(result); } /////////////////////////////////////////////////////////////////////// // This is an example returns "True" or False. RECITAL_FUNCTION fnSamplesLogical(void) { char result[10]; int arg1; if (!_parse_parameters(PCOUNT, "L", &arg1)) { ERROR(-1, "Incorrect parameters"); } if (arg1) strcpy(result, "True"); else strcpy(result, "False"); _retc(result); } /////////////////////////////////////////////////////////////////////// // This example opens a table. RECITAL_FUNCTION fnSamplesOpen(void) { char *arg1; if (!_parse_parameters(PCOUNT, "C", &arg1)) { ERROR(-1, "Incorrect parameters"); } if (_parinfo(1) == API_CTYPE) { _retni(COMMAND(arg1)); } else { _retni(-1); } } /////////////////////////////////////////////////////////////////////// // Define the MyClass CLASS using the API macros /////////////////////////////////////////////////////////////////////// RECITAL_EXPORT int DEFINE_CLASS(clsMyClass) { /*-------------------------------------*/ /* Dispatch factory methods and return */ /*-------------------------------------*/ DISPATCH_FACTORY(); /*---------------------------------*/ /* Dispatch constructor and return */ /*---------------------------------*/ DISPATCH_METHOD(clsMyClass, Constructor); /*--------------------------------*/ /* Dispatch destructor and return */ /*--------------------------------*/ DISPATCH_METHOD(clsMyClass, Destructor); /*-----------------------------------*/ /* Dispatch DEFINE method and return */ /*-----------------------------------*/ DISPATCH_METHOD(clsMyClass, Define); /*------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property NumValue */ /* then return. */ /*------------------------------*/ DISPATCH_PROPSET(clsMyClass, NumValue); DISPATCH_PROPGET(clsMyClass, NumValue); /*------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property LogValue */ /* then return. */ /*------------------------------*/ DISPATCH_PROPSET(clsMyClass, LogValue); DISPATCH_PROPGET(clsMyClass, LogValue); /*-------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property DateValue */ /* then return. */ /*-------------------------------*/ DISPATCH_PROPSET(clsMyClass, DateValue); DISPATCH_PROPGET(clsMyClass, DateValue); /*-------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property TimeValue */ /* then return. */ /*-------------------------------*/ DISPATCH_PROPSET(clsMyClass, TimeValue); DISPATCH_PROPGET(clsMyClass, TimeValue); /*-------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property CurrValue */ /* then return. */ /*-------------------------------*/ DISPATCH_PROPSET(clsMyClass, CurrValue); DISPATCH_PROPGET(clsMyClass, CurrValue); /*-------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property CharValue */ /* then return. */ /*-------------------------------*/ DISPATCH_PROPSET(clsMyClass, CharValue); DISPATCH_PROPGET(clsMyClass, CharValue); /*------------------------------*/ /* Dispatch SET or GET PROPERTY */ /* method for property ObjValue */ /* then return. */ /*------------------------------*/ DISPATCH_PROPSET(clsMyClass, ObjValue); DISPATCH_PROPGET(clsMyClass, ObjValue); /*-----------------------------------*/ /* If message not found return error */ /*-----------------------------------*/ OBJECT_RETERROR("Unknown message type"); } //////////////////////////////////////////////////////////////////////////////// // Define METHOD handlers //////////////////////////////////////////////////////////////////////////////// DEFINE_METHOD(clsMyClass, Constructor) { struct example_data *objectDataArea; /* Allocate memory for objects objectData area */ objectDataArea = (struct example_data *) malloc(sizeof(struct example_data)); if (objectDataArea == NULL) return(-1); /* Assign the default property values */ strcpy(objectDataArea->prop_charvalue, "Test API object"); objectDataArea->prop_numvalue = 15.2827; objectDataArea->prop_logvalue = 'F'; strcpy(objectDataArea->prop_datevalue, DATE_DATE()); strcpy(objectDataArea->prop_timevalue, DATE_DATETIME()); strcpy(objectDataArea->prop_currvalue, "15.2827"); strcpy(objectDataArea->object_name, "APIobject"); objectDataArea->prop_objvalue = OBJECT_NEW(objectDataArea->object_name, "exception", NULL); /* Set the object objectData area */ OBJECT_SETDATA((char *)objectDataArea); return(0); } DEFINE_METHOD(clsMyClass, Destructor) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData != NULL) { if (objectData->prop_objvalue != NULL) OBJECT_DELETE(objectData->prop_objvalue); free(objectData); objectData = NULL; } return(0); } DEFINE_METHOD(clsMyClass, Define) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; char buffer[512]; int rc; /* Check the object class */ OBJECT_GETPROPERTY(objectData->prop_objvalue, "class", buffer); rc = OBJECT_GETARG(buffer, &result); if (result.errno == 0 && result.type == 'C' && strcmp(result.character, "Exception") == 0) { switch (OBJECT_GETARGC()) { case 1: rc = OBJECT_GETPARAMETER(1, &result); if (result.errno == 0 && result.type == 'C') { OBJECT_SETARG(buffer, &result); rc = OBJECT_SETPROPERTY(objectData->prop_objvalue, "message", buffer); } break; case 2: rc = OBJECT_GETPARAMETER(2, &result); if (result.errno == 0 && result.type == 'N') { OBJECT_SETARG(buffer, &result); rc = OBJECT_SETPROPERTY(objectData->prop_objvalue, "errorno", buffer); } } } result.type = 'L'; result.logical = (rc == 0 ? 'T' : 'F'); OBJECT_RETRESULT(&result); } //////////////////////////////////////////////////////////////////////////////// // Define GET property handlers //////////////////////////////////////////////////////////////////////////////// DEFINE_PROPERTYGET(clsMyClass, NumValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('N', objectData->prop_numvalue); } DEFINE_PROPERTYGET(clsMyClass, LogValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('L', objectData->prop_logvalue); } DEFINE_PROPERTYGET(clsMyClass, DateValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('D', objectData->prop_datevalue); } DEFINE_PROPERTYGET(clsMyClass, TimeValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('T', objectData->prop_timevalue); } DEFINE_PROPERTYGET(clsMyClass, CurrValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('Y', objectData->prop_currvalue); } DEFINE_PROPERTYGET(clsMyClass, CharValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('C', objectData->prop_charvalue); } DEFINE_PROPERTYGET(clsMyClass, ObjValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); if (objectData == NULL) return(-1); OBJECT_RETPROPERTY('O', objectData->prop_objvalue); } //////////////////////////////////////////////////////////////////////////////// // Define SET property handlers //////////////////////////////////////////////////////////////////////////////// DEFINE_PROPERTYSET(clsMyClass, NumValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'N') { objectData->prop_numvalue = result.number; rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, LogValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'L') { objectData->prop_logvalue = result.logical; rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, DateValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'D') { strcpy(objectData->prop_datevalue, DATE_DTOS(result.date)); rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, TimeValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'T') { strcpy(objectData->prop_timevalue, DATE_TTOS(result.datetime)); rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, CurrValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'Y') { strcpy(objectData->prop_currvalue, CURR_YTOS(result.currency)); rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, CharValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); struct API_EXPRESSION result; int rc = OBJECT_ERROR; OBJECT_GETVALUE(&result); if (result.errno == 0 && result.type == 'C') { strcpy(objectData->prop_currvalue, result.character); rc = OBJECT_SUCCESS; } return(rc); } DEFINE_PROPERTYSET(clsMyClass, ObjValue) { struct example_data *objectData = (struct example_data *)OBJECT_GETDATA(); OBJECT objvalue; int rc = OBJECT_ERROR; if (OBJECT_GETTYPE() == 'O') { objvalue = OBJECT_GETOBJECT(); objectData->prop_objvalue = OBJECT_ASSIGN(objvalue, objectData->object_name); rc = OBJECT_SUCCESS; } return(rc); }