loop table_name(col1, coll2, ...) function function_name(param1, param2, ...)
update (updated_col1, updated_col1, ...)
where ...
Parameters, update clause and where clause are optional
SB will execute 3 C functions
1/ <function_name>_before: optional "before function" called once, where you allocated the object you will need
2/ <function_name>: mandatory "loop function" called once per line fecthed, where you populate the objects
3/ <function_name>_after: optional "after function" called once, where you free memory
See input/output of these functions in common.h
./_SO_CODE/common.h
typedef struct sb_vals {
 // SB's version of the SB_VALS structure
 int sb_vals_version;
 // number of columns
 int count;
 // i_val of each column (loop function only)
 // NULL value: NULL_IVAL (see define here above)
 U_INT *i_vals;
 // num_val of each column (loop function only)
 // NULL value: NAN (C macro) (any float for which isnan evals to true)
 float *num_vals;
 // str_val of each column (loop function only)
 // NULL value: NULL (C macro) or SB_NULL (see define here above)
 char **str_vals;
 // type of each column, 2 possible values T (text) or N (number)
 char *types;
 // obvious
 char *table_name;
 // obvious
 char **column_names;
 // obvious
 char **col_type_names;
 // number of distinct i_val of each column
 U_INT *col_i_val_counts;
 // number of lines in table
 // (where clause is not taken into account)
 // (partitions are taken into account)
 long table_line_count;
 // (in/out) type used to update the columns type of each column,
 // 3 possible values: T (text) or N (number) or I (i_val)
 char *update_types;
 // (in/out) whatever pointer you write here in the before function will be passed to the loop/after functions
 void *context;
 // (in/out) response of the loop statement
 char *result;
 // (in/out) #threads that SB will create, default is 1, of course your code must be thread safe if you put 2+
 int thread_count; //SB_LIB_VERSION >= 1
 // current thread (loop function only)
 int thread_pos; //SB_LIB_VERSION >= 1
 // if you need a mutex in the loop function, you can use this one
 // obsolete, do not use (kept for compatibility matters)
 pthread_mutex_t loop_mutex; //SB_LIB_VERSION >= 2
 // parameters passed to the loop function (parameters and columns are not the same thing!)
 char **params; //SB_LIB_VERSION >= 3
 // #parameters passed to the loop function (parameters and columns are not the same thing!)
 int params_len; //SB_LIB_VERSION >= 3
 //in out fields (can be modified in before function):
 // thread_count (default 1)
 // (in/out) defines which partition should be fetch (default ALL_PARTITIONS, can be set to DIRTY_PARTITIONS_ONLY)
 int partitions_scope; //SB_LIB_VERSION >= 4
 // (in/out) defines which lines should be fetch (default ALL_LINES, can be set to LINES_INSERTED_SINCE_LAST_REFRESH_DIRTY_ONLY)
 int lines_scope; //SB_LIB_VERSION >= 5
 // context field behavior:
 // Note that the context is global (there is only one "loop context" at a given time: "the current loop context")
 // - pass by stormbase to all loop command
 // - the before function can set the context (any non NULL value will be considered as the current loop context)
 // - the after function can set the context to NULL and usually frees memory at the same time (NULL value will be considered as "setting the current loop context to NULL")
 // - any function can manipulate the current loop context, it is standard C manipulation
 // - if the before function sets a name to the current loop context, it can be recall later (in computed columns). Of course in that case the after function should not free it.
 char *context_name; //SB_LIB_VERSION >= 6
 // see structure definition
 UTILITY *U; //SB_LIB_VERSION >= 7
 // (in/out) optional name of the function to be called to free the context if any (added in v1.16.81)
 // this fn must be t_fn_free_context type
 char *fn_free_context_name; //SB_LIB_VERSION >= 8
 // (in/out) if update_types uses text update (type T), y means SB will free the pointer, default n
 // Note: many malloc/free will generate memory fragmentation at OS level, so it is better to manage a buffer in your custom code for text updates
 char text_update_sb_free; //SB_LIB_VERSION >= 10
 // (in/out) whatever pointer you write here in the before function will be passed to the loop/after functions,
 // the work_area can be considered as a second context except that it can't be called after the loop execution
 void *work_area; //SB_LIB_VERSION >= 13
 // (in) unique line number provide by SB during the fetch, note that line is always < table_line_count
 // the goal in to facilitate multi threading, you don't have to manage a counter with a mutex in your code
 // Note: a given line in a table may receive a different line_fetch number in distinct loop execution
 // Note: in after fn line_fetch will be equal to the number of line fetch
 long line_fetch; //SB_LIB_VERSION >= 14
} SB_VALS;
// before function (returns OK or KO)
typedef int (*t_fn_line_before)(SB_VALS *read, SB_VALS *new);
// loop function (returns DELETED or UPDATED or NOT_MODIFIED)
typedef int (*t_fn_line)(SB_VALS *read, SB_VALS *new);
// after function (returns OK or KO)
typedef int (*t_fn_line_after)(SB_VALS *read, SB_VALS *new);
// free function
typedef void (*t_fn_free_context)(void *context);
//
./_SO_CODE/Z_doc_loop_AA.c
#include "./common.h"
int fn1_before(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_before\n" );
 read->thread_count = 2;
 return OK;
}
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
int fn1(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1 (thread_pos %d) : " , read->thread_pos);
 for (int i = 0; i < read-> count; i++) {
   if (read->types[i] == 'T') {
     printf("%s=%s (ival %u)" , read->column_names[i], read->str_vals[i], read->i_vals[i]);
   } else if (read->types[i] == 'N') {
     printf("%s=%.2f (ival %u)" , read->column_names[i], read->num_vals[i], read->i_vals[i]);
   }
   if (i != read->count - 1) {
     printf(", " );
   }
 }
 printf("\n" );
 return NOT_MODIFIED;
}
int fn1_after(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_after\n" );
 return OK;
}
set SO_FILE_NAME='Z_doc_loop_AA.so';
refresh dirty view;
loop sales(item_id, sales_qty, line_id) function fn1;
hello from fn1_before
hello from fn1 (thread_pos 0) : item_id=artA (ival 11), sales_qty=5.00 (ival 10), line_id=ID01 (ival 10)
hello from fn1 (thread_pos 0) : item_id=artB (ival 12), sales_qty=6.00 (ival 11), line_id=ID02 (ival 11)
hello from fn1 (thread_pos 0) : item_id=artB (ival 12), sales_qty=4.00 (ival 12), line_id=ID03 (ival 12)
hello from fn1 (thread_pos 0) : item_id=artB (ival 12), sales_qty=7.00 (ival 13), line_id=ID04 (ival 13)
hello from fn1 (thread_pos 0) : item_id=artC (ival 14), sales_qty=8.00 (ival 14), line_id=ID05 (ival 14)
hello from fn1 (thread_pos 0) : item_id=artA (ival 11), sales_qty=5.00 (ival 10), line_id=ID06 (ival 15)
hello from fn1 (thread_pos 0) : item_id=artA (ival 11), sales_qty=5.00 (ival 10), line_id=ID07 (ival 16)
hello from fn1_after
./_SO_CODE/Z_doc_loop_BB.c
#include "./common.h"
int fn1_before(SB_VALS* read, SB_VALS* new) {
 read->thread_count = 2;
 int *c = malloc(sizeof(int));
 *c = 99;
 read->context = c;
 new->update_types[0] = 'T';
 return OK;
}
char txt_th0[20];
char txt_th1[20];
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
int fn1(SB_VALS* read, SB_VALS* new) {
 int *c = read->context;
 char *txt = read->thread_pos == 0 ? txt_th0 : txt_th1;
 sprintf(txt, "NEW_ID_%d" , *c);
 *c = *c - 1;
 new->str_vals[0] = txt;
 return UPDATED;
}
int fn1_after(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_after\n" );
 free(read->context);
 return OK;
}
set SO_FILE_NAME='Z_doc_loop_BB.so';
refresh dirty view;
select item_id, sales_qty, line_id from v_sales cb sort(1,'asc');
| item_id | sales_qty | line_id |
| # | 8 | ID05 |
| artA | 5 | ID06 |
| artA | 5 | ID07 |
| artA | 5 | ID01 |
| artB | 6 | ID02 |
| artB | 4 | ID03 |
| artB | 7 | ID04 |
loop sales(item_id, sales_qty) function fn1 update(line_id) where item_id='artA';
hello from fn1_after
refresh dirty view;
select item_id, sales_qty, line_id from v_sales cb sort(1,'asc');
| item_id | sales_qty | line_id |
| # | 8 | ID05 |
| artA | 5 | NEW_ID_97 |
| artA | 5 | NEW_ID_98 |
| artA | 5 | NEW_ID_99 |
| artB | 6 | ID02 |
| artB | 4 | ID03 |
| artB | 7 | ID04 |
./_SO_CODE/Z_doc_loop_AB.c
#include "./common.h"
int fn1_before(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_before\n" );
 for(int i=0;i params_len;i++){
   printf("## fn1_before param %d is %s\n" ,i+1,read->params[i]);
 }
 read->thread_count = 2;
 return OK;
}
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
int fn1(SB_VALS* read, SB_VALS* new) {
 return NOT_MODIFIED;
}
int fn1_after(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_after\n" );
 return OK;
}
set SO_FILE_NAME='Z_doc_loop_AB.so';
refresh dirty view;
loop sales(item_id, sales_qty, line_id) function fn1('val1','val2');
hello from fn1_before
fn1_before param 1 is val1
fn1_before param 2 is val2
hello from fn1_after
./_SO_CODE/Z_doc_loop_without_mutex.c
#include "./common.h"
int fn1_before(SB_VALS* read, SB_VALS* new) {
 read->thread_count = 2;
 return OK;
}
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
int fn1(SB_VALS* read, SB_VALS* new) {
 int r=rand()%1000000;
 printf("## fn1 A %p %d\n" ,&r,read->thread_pos);
 usleep(r);
 printf("## fn1 B\n" );
 return NOT_MODIFIED;
}
int fn1_after(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_after\n" );
 return OK;
}
set SO_FILE_NAME='Z_doc_loop_without_mutex.so';
refresh dirty view;
loop sales(item_id, sales_qty) function fn1;
fn1 A 0x7000002c6d4c 1
fn1 A 0x700000243d4c 0
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x700000243d4c 0
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 B
hello from fn1_after
./_SO_CODE/Z_doc_loop_with_mutex.c
#include "./common.h"
int fn1_before(SB_VALS* read, SB_VALS* new) {
 read->thread_count = 2;
 return OK;
}
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
int fn1(SB_VALS* read, SB_VALS* new) {
 int r=rand()%1000000;
 pthread_mutex_lock(&my_mutex);
 printf("## fn1 A %p %d\n" ,&r,read->thread_pos);
 usleep(r);
 printf("## fn1 B\n" );
 pthread_mutex_unlock(&my_mutex);
 return NOT_MODIFIED;
}
int fn1_after(SB_VALS* read, SB_VALS* new) {
 printf("## hello from fn1_after\n" );
 return OK;
}
set SO_FILE_NAME='Z_doc_loop_with_mutex.so';
refresh dirty view;
loop sales(item_id, sales_qty) function fn1;
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x7000002c6d4c 1
fn1 B
fn1 A 0x700000243d4c 0
fn1 B
fn1 A 0x700000243d4c 0
fn1 B
hello from fn1_after