Pārlūkot izejas kodu

htable: allow specifying column names per hash table

- at least two columns must be specified, first is the one corresponding
  to key_name
- the values of following columns are concatenated with a comma delimiter
- columns can be specified with cols attribute in htable definition and
  they must be enclosed in quotes in order to be a valid sip parameter
  value and be separated by comma

modparam("htable", "htable",
    "a=>size=4;dbtable=a;cols='key_name,val1,val2,val3'")
Daniel-Constantin Mierla 9 gadi atpakaļ
vecāks
revīzija
955455aad0
3 mainītis faili ar 280 papildinājumiem un 145 dzēšanām
  1. 53 3
      modules/htable/ht_api.c
  2. 7 2
      modules/htable/ht_api.h
  3. 220 140
      modules/htable/ht_db.c

+ 53 - 3
modules/htable/ht_api.c

@@ -251,11 +251,14 @@ ht_t* ht_get_table(str *name)
 	return NULL;
 }
 
-int ht_add_table(str *name, int autoexp, str *dbtable, int size, int dbmode,
-		int itype, int_str *ival, int updateexpire, int dmqreplicate)
+int ht_add_table(str *name, int autoexp, str *dbtable, str *dbcols, int size,
+		int dbmode, int itype, int_str *ival, int updateexpire,
+		int dmqreplicate)
 {
 	unsigned int htid;
 	ht_t *ht;
+	int c;
+	int i;
 
 	htid = ht_compute_hash(name);
 
@@ -296,6 +299,48 @@ int ht_add_table(str *name, int autoexp, str *dbtable, int size, int dbmode,
 	if(ival!=NULL)
 		ht->initval = *ival;
 	ht->dmqreplicate = dmqreplicate;
+
+	if(dbcols!=NULL && dbcols->s!=NULL && dbcols->len>0) {
+		ht->scols[0].s = (char*)shm_malloc((1+dbcols->len)*sizeof(char));
+		if(ht->scols[0].s==NULL) {
+			LM_ERR("no more shm memory\n");
+			shm_free(ht);
+			return -1;
+		}
+		memset(ht->scols[0].s, 0, (1+dbcols->len)*sizeof(char));
+		memcpy(ht->scols[0].s, dbcols->s, dbcols->len);
+		ht->scols[0].len = dbcols->len;
+		c = 0;
+		for(i=0; i<dbcols->len; i++) {
+			if(ht->scols[0].s[i]==',') {
+				ht->scols[c].len = (ht->scols[0].s + i - ht->scols[c].s);
+				LM_DBG("db table column[%d]='%.*s'\n", c,
+						ht->scols[c].len, ht->scols[c].s);
+				c++;
+				if(c>=HT_MAX_COLS) {
+					LM_ERR("too many columns %d\n", c);
+					shm_free(ht->scols[0].s);
+					shm_free(ht);
+					return -1;
+				}
+				ht->scols[c].s = ht->scols[0].s + i + 1;
+				ht->scols[c].len = ht->scols[0].s + dbcols->len - ht->scols[c].s;
+			}
+		}
+		LM_DBG("db table column[%d]='%.*s'\n", c,
+				ht->scols[c].len, ht->scols[c].s);
+		if(c==0) {
+			LM_ERR("there must be at least two columns (prefix, value)\n");
+			shm_free(ht->scols[0].s);
+			shm_free(ht);
+			return -1;
+		}
+		ht->ncols = c + 1;
+		ht->pack[0] = 'l';
+		ht->pack[1] = ',';
+		ht->pack[2] = '*';
+	}
+
 	ht->next = _ht_root;
 	_ht_root = ht;
 	return 0;
@@ -828,6 +873,7 @@ int ht_table_spec(char *spec)
 	keyvalue_t kval;
 	str name;
 	str dbtable = {0, 0};
+	str dbcols  = {0, 0};
 	unsigned int autoexpire = 0;
 	unsigned int size = 4;
 	unsigned int dbmode = 0;
@@ -863,6 +909,10 @@ int ht_table_spec(char *spec)
 			dbtable = tok;
 			LM_DBG("htable [%.*s] - dbtable [%.*s]\n", name.len, name.s,
 					dbtable.len, dbtable.s);
+		} else if(pit->name.len==4 && strncmp(pit->name.s, "cols", 4)==0) {
+			dbcols = tok;
+			LM_DBG("htable [%.*s] - dbcols [%.*s]\n", name.len, name.s,
+					dbcols.len, dbcols.s);
 		} else if(pit->name.len==10 && strncmp(pit->name.s, "autoexpire", 10)==0) {
 			if(str2int(&tok, &autoexpire)!=0)
 				goto error;
@@ -897,7 +947,7 @@ int ht_table_spec(char *spec)
 		} else { goto error; }
 	}
 
-	return ht_add_table(&name, autoexpire, &dbtable, size, dbmode,
+	return ht_add_table(&name, autoexpire, &dbtable, &dbcols, size, dbmode,
 			itype, &ival, updateexpire, dmqreplicate);
 
 error:

+ 7 - 2
modules/htable/ht_api.h

@@ -53,6 +53,7 @@ typedef struct _ht_entry
 	int rec_lock_level;  /* recursive lock count */
 } ht_entry_t;
 
+#define HT_MAX_COLS 8
 typedef struct _ht
 {
 	str name;
@@ -60,6 +61,9 @@ typedef struct _ht
 	unsigned int htexpire;
 	str dbtable;
 	int dbmode;
+	int ncols;
+	str scols[HT_MAX_COLS];
+	char pack[4];
 	int flags;
 	int_str initval;
 	int updateexpire;
@@ -76,8 +80,9 @@ typedef struct _ht_pv {
 	pv_elem_t *pve;
 } ht_pv_t, *ht_pv_p;
 
-int ht_add_table(str *name, int autoexp, str *dbtable, int size, int dbmode,
-		int itype, int_str *ival, int updateexpire, int dmqreplicate);
+int ht_add_table(str *name, int autoexp, str *dbtable, str *dbcols, int size,
+		int dbmode, int itype, int_str *ival, int updateexpire,
+		int dmqreplicate);
 int ht_init_tables(void);
 int ht_destroy(void);
 int ht_set_cell(ht_t *ht, str *name, int type, int_str *val, int mode);

+ 220 - 140
modules/htable/ht_db.c

@@ -111,13 +111,68 @@ int ht_db_close_con(void)
 #define HT_NAME_BUF_SIZE	256
 static char ht_name_buf[HT_NAME_BUF_SIZE];
 
+static int ht_pack_values(ht_t *ht, db1_res_t* db_res,
+		int row, int cols, str *hvalue)
+{
+	static char vbuf[4096];
+	int c;
+	int len;
+	char *p;
+	str iv;
+
+	len = 0;
+	for(c=1; c<cols; c++) {
+		if(VAL_NULL(&RES_ROWS(db_res)[row].values[c])) {
+			len += 1;
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_STRING) {
+			len += strlen(RES_ROWS(db_res)[row].values[c].val.string_val);
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_STR) {
+			len += RES_ROWS(db_res)[row].values[c].val.str_val.len;
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_INT) {
+			len += 12;
+		} else {
+			LM_ERR("unsupported data type for column %d\n", c);
+			return -1;
+		}
+	}
+	if(len + c>=4096) {
+		LM_ERR("too large values (need %d)\n", len+c);
+		return -1;
+	}
+	p = vbuf;
+	for(c=1; c<cols; c++) {
+		if(VAL_NULL(&RES_ROWS(db_res)[row].values[c])) {
+			*p = ht->pack[2];
+			p++;
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_STRING) {
+			strcpy(p, RES_ROWS(db_res)[row].values[c].val.string_val);
+			p += strlen(RES_ROWS(db_res)[row].values[c].val.string_val);
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_STR) {
+			strncpy(p, RES_ROWS(db_res)[row].values[c].val.str_val.s,
+				RES_ROWS(db_res)[row].values[c].val.str_val.len);
+			p += RES_ROWS(db_res)[row].values[c].val.str_val.len;
+		} else if(RES_ROWS(db_res)[row].values[c].type == DB1_INT) {
+			iv.s = sint2str(RES_ROWS(db_res)[row].values[c].val.int_val, &iv.len);
+			strncpy(p, iv.s, iv.len);
+			p += iv.len;
+		}
+		if(c+1<cols) {
+			*p = ht->pack[1];
+			p++;
+		}
+	}
+	hvalue->s = vbuf;
+	hvalue->len = p - vbuf;
+	LM_DBG("packed: [%.*s]\n", hvalue->len, hvalue->s);
+	return 0;
+}
+
 /**
  * load content of a db table in hash table
  */
 int ht_db_load_table(ht_t *ht, str *dbtable, int mode)
 {
-	db_key_t db_cols[5] = {&ht_db_name_column, &ht_db_ktype_column,
-		&ht_db_vtype_column, &ht_db_value_column, &ht_db_expires_column};
+	db_key_t db_cols[HT_MAX_COLS];
 	db_key_t db_ord = &ht_db_name_column;
 	db1_res_t* db_res = NULL;
 	str kname;
@@ -135,6 +190,7 @@ int ht_db_load_table(ht_t *ht, str *dbtable, int mode)
 	int cnt;
 	int now;
 	int ncols;
+	int c;
 
 	if(ht_db_con==NULL)
 	{
@@ -147,13 +203,25 @@ int ht_db_load_table(ht_t *ht, str *dbtable, int mode)
 		LM_ERR("failed to use_table\n");
 		return -1;
 	}
+	if(ht->ncols>0) {
+		for(c=0; c<ht->ncols; c++) {
+			db_cols[c] = &ht->scols[c];
+		}
+		ncols = c;
+	} else {
+		db_cols[0] = &ht_db_name_column;
+		db_cols[1] = &ht_db_ktype_column;
+		db_cols[2] = &ht_db_vtype_column;
+		db_cols[3] = &ht_db_value_column;
+		db_cols[4] = &ht_db_expires_column;
+		ncols = 4;
+		if(ht->htexpire > 0 && ht_db_expires_flag!=0)
+			ncols = 5;
+	}
 
 	LM_DBG("=============== loading hash table [%.*s] from database [%.*s]\n",
 			ht->name.len, ht->name.s, dbtable->len, dbtable->s);
 	cnt = 0;
-	ncols = 4;
-	if(ht->htexpire > 0 && ht_db_expires_flag!=0)
-		ncols = 5;
 
 	if (DB_CAPABILITY(ht_dbf, DB_CAP_FETCH)) {
 		if(ht_dbf.query(ht_db_con,0,0,0,db_cols,0,ncols,db_ord,0) < 0)
@@ -215,161 +283,173 @@ int ht_db_load_table(ht_t *ht, str *dbtable, int mode)
 			}
 			kname.len = strlen(kname.s);
 
-			expires.n = 0;
-			if(ht->htexpire > 0 && ht_db_expires_flag!=0) {
-				expires.n = RES_ROWS(db_res)[i].values[4].val.int_val;
-				if (expires.n > 0 && expires.n < now) {
-					LM_DBG("skipping expired entry [%.*s] (%d)\n", kname.len,
-							kname.s, expires.n-now);
-					continue;
+			if(ht->ncols>0) {
+				if(ht_pack_values(ht, db_res, i, ncols, &val.s)<0) {
+					LM_ERR("Error packing values\n");
+					goto error;
 				}
-			}
-
-			cnt++;
-			switch(RES_ROWS(db_res)[i].values[1].type)
-			{
-			case DB1_INT:
-				ktype = RES_ROWS(db_res)[i].values[1].val.int_val;
-				break;
-			case DB1_BIGINT:
-				ktype = RES_ROWS(db_res)[i].values[1].val.ll_val;
-				break;
-			default:
-				LM_ERR("Wrong db type [%d] for key_type column\n",
-					RES_ROWS(db_res)[i].values[1].type);
-				goto error;
-			}
-			if(last_ktype==1)
-			{
-				if(pname.len>0
-						&& (pname.len!=kname.len
-							|| strncmp(pname.s, kname.s, pname.len)!=0))
+				if(ht_set_cell(ht, &kname, AVP_VAL_STR, &val, mode))
 				{
-					/* new key name, last was an array => add its size */
-					snprintf(ht_name_buf, HT_NAME_BUF_SIZE, "%.*s%.*s",
-						pname.len, pname.s, ht_array_size_suffix.len,
-						ht_array_size_suffix.s);
-					hname.s = ht_name_buf;
-					hname.len = strlen(ht_name_buf);
-					val.n = n;
-
-					if(ht_set_cell(ht, &hname, 0, &val, mode))
-					{
-						LM_ERR("error adding array size to hash table.\n");
-						goto error;
-					}
-					pname.len = 0;
-					pname.s = "";
-					n = 0;
+					LM_ERR("error adding to hash table\n");
+					goto error;
 				}
-			}
-			last_ktype = ktype;
-			pname = kname;
-			if(ktype==1)
-			{
-				snprintf(ht_name_buf, HT_NAME_BUF_SIZE, "%.*s[%d]",
-						kname.len, kname.s, n);
-				hname.s = ht_name_buf;
-				hname.len = strlen(ht_name_buf);
-				n++;
 			} else {
-				hname = kname;
-			}
-			switch(RES_ROWS(db_res)[i].values[2].type)
-			{
-			case DB1_INT:
-				vtype = RES_ROWS(db_res)[i].values[2].val.int_val;
-				break;
-			case DB1_BIGINT:
-				vtype = RES_ROWS(db_res)[i].values[2].val.ll_val;
-				break;
-			default:
-				LM_ERR("Wrong db type [%d] for value_type column\n",
-					RES_ROWS(db_res)[i].values[2].type);
-				goto error;
-			}
+				expires.n = 0;
+				if(ht->htexpire > 0 && ht_db_expires_flag!=0) {
+					expires.n = RES_ROWS(db_res)[i].values[4].val.int_val;
+					if (expires.n > 0 && expires.n < now) {
+						LM_DBG("skipping expired entry [%.*s] (%d)\n", kname.len,
+								kname.s, expires.n-now);
+						continue;
+					}
+				}
 
-			/* add to hash */
-			if(vtype==1)
-			{
-				switch(RES_ROWS(db_res)[i].values[3].type)
+				cnt++;
+				switch(RES_ROWS(db_res)[i].values[1].type)
 				{
-				case DB1_STR:
-					kvalue = RES_ROWS(db_res)[i].values[3].val.str_val;
-					if(kvalue.s==NULL) {
-						LM_ERR("null value in row %d\n", i);
-						goto error;
-					}
-					str2sint(&kvalue, &val.n);
-					break;
-				case DB1_STRING:
-					kvalue.s = (char*)(RES_ROWS(db_res)[i].values[3].val.string_val);
-					if(kvalue.s==NULL) {
-						LM_ERR("null value in row %d\n", i);
-						goto error;
-					}
-					kvalue.len = strlen(kvalue.s);
-					str2sint(&kvalue, &val.n);
-					break;
-				case DB1_INT:
-					val.n = RES_ROWS(db_res)[i].values[3].val.int_val;
+					case DB1_INT:
+						ktype = RES_ROWS(db_res)[i].values[1].val.int_val;
 					break;
 				case DB1_BIGINT:
-					val.n = RES_ROWS(db_res)[i].values[3].val.ll_val;
+					ktype = RES_ROWS(db_res)[i].values[1].val.ll_val;
 					break;
 				default:
-					LM_ERR("Wrong db type [%d] for key_value column\n",
-						RES_ROWS(db_res)[i].values[3].type);
+					LM_ERR("Wrong db type [%d] for key_type column\n",
+						RES_ROWS(db_res)[i].values[1].type);
 					goto error;
 				}
-			} else {
-				switch(RES_ROWS(db_res)[i].values[3].type)
+				if(last_ktype==1)
 				{
-				case DB1_STR:
-					kvalue = RES_ROWS(db_res)[i].values[3].val.str_val;
-					if(kvalue.s==NULL) {
-						LM_ERR("null value in row %d\n", i);
-						goto error;
+					if(pname.len>0
+							&& (pname.len!=kname.len
+								|| strncmp(pname.s, kname.s, pname.len)!=0))
+					{
+						/* new key name, last was an array => add its size */
+						snprintf(ht_name_buf, HT_NAME_BUF_SIZE, "%.*s%.*s",
+								pname.len, pname.s, ht_array_size_suffix.len,
+								ht_array_size_suffix.s);
+						hname.s = ht_name_buf;
+						hname.len = strlen(ht_name_buf);
+						val.n = n;
+
+						if(ht_set_cell(ht, &hname, 0, &val, mode))
+						{
+							LM_ERR("error adding array size to hash table.\n");
+							goto error;
+						}
+						pname.len = 0;
+						pname.s = "";
+						n = 0;
 					}
-					val.s = kvalue;
-					break;
-				case DB1_STRING:
-					kvalue.s = (char*)(RES_ROWS(db_res)[i].values[3].val.string_val);
-					if(kvalue.s==NULL) {
-						LM_ERR("null value in row %d\n", i);
+				}
+				last_ktype = ktype;
+				pname = kname;
+				if(ktype==1)
+				{
+					snprintf(ht_name_buf, HT_NAME_BUF_SIZE, "%.*s[%d]",
+							kname.len, kname.s, n);
+					hname.s = ht_name_buf;
+					hname.len = strlen(ht_name_buf);
+					n++;
+				} else {
+					hname = kname;
+				}
+				switch(RES_ROWS(db_res)[i].values[2].type)
+				{
+					case DB1_INT:
+						vtype = RES_ROWS(db_res)[i].values[2].val.int_val;
+						break;
+					case DB1_BIGINT:
+						vtype = RES_ROWS(db_res)[i].values[2].val.ll_val;
+						break;
+					default:
+						LM_ERR("Wrong db type [%d] for value_type column\n",
+								RES_ROWS(db_res)[i].values[2].type);
 						goto error;
+				}
+
+				/* add to hash */
+				if(vtype==1)
+				{
+					switch(RES_ROWS(db_res)[i].values[3].type)
+					{
+						case DB1_STR:
+							kvalue = RES_ROWS(db_res)[i].values[3].val.str_val;
+							if(kvalue.s==NULL) {
+								LM_ERR("null value in row %d\n", i);
+								goto error;
+							}
+							str2sint(&kvalue, &val.n);
+							break;
+						case DB1_STRING:
+							kvalue.s = (char*)(RES_ROWS(db_res)[i].values[3].val.string_val);
+							if(kvalue.s==NULL) {
+								LM_ERR("null value in row %d\n", i);
+								goto error;
+							}
+							kvalue.len = strlen(kvalue.s);
+							str2sint(&kvalue, &val.n);
+							break;
+						case DB1_INT:
+							val.n = RES_ROWS(db_res)[i].values[3].val.int_val;
+							break;
+						case DB1_BIGINT:
+							val.n = RES_ROWS(db_res)[i].values[3].val.ll_val;
+							break;
+						default:
+							LM_ERR("Wrong db type [%d] for key_value column\n",
+									RES_ROWS(db_res)[i].values[3].type);
+							goto error;
+					}
+				} else {
+					switch(RES_ROWS(db_res)[i].values[3].type)
+					{
+						case DB1_STR:
+							kvalue = RES_ROWS(db_res)[i].values[3].val.str_val;
+							if(kvalue.s==NULL) {
+								LM_ERR("null value in row %d\n", i);
+								goto error;
+							}
+							val.s = kvalue;
+							break;
+						case DB1_STRING:
+							kvalue.s = (char*)(RES_ROWS(db_res)[i].values[3].val.string_val);
+							if(kvalue.s==NULL) {
+								LM_ERR("null value in row %d\n", i);
+								goto error;
+							}
+							kvalue.len = strlen(kvalue.s);
+							val.s = kvalue;
+							break;
+						case DB1_INT:
+							kvalue.s = int2str(RES_ROWS(db_res)[i].values[3].val.int_val, &kvalue.len);
+							val.s = kvalue;
+							break;
+						case DB1_BIGINT:
+							kvalue.s = int2str(RES_ROWS(db_res)[i].values[3].val.ll_val, &kvalue.len);
+							val.s = kvalue;
+							break;
+						default:
+							LM_ERR("Wrong db type [%d] for key_value column\n",
+									RES_ROWS(db_res)[i].values[3].type);
+							goto error;
 					}
-					kvalue.len = strlen(kvalue.s);
-					val.s = kvalue;
-					break;
-				case DB1_INT:
-					kvalue.s = int2str(RES_ROWS(db_res)[i].values[3].val.int_val, &kvalue.len);
-					val.s = kvalue;
-					break;
-				case DB1_BIGINT:
-					kvalue.s = int2str(RES_ROWS(db_res)[i].values[3].val.ll_val, &kvalue.len);
-					val.s = kvalue;
-					break;
-				default:
-					LM_ERR("Wrong db type [%d] for key_value column\n",
-						RES_ROWS(db_res)[i].values[3].type);
-					goto error;
 				}
-			}
 				
-			if(ht_set_cell(ht, &hname, (vtype)?0:AVP_VAL_STR, &val, mode))
-			{
-				LM_ERR("error adding to hash table\n");
-				goto error;
-			}
-
-			/* set expiry */
-			if (ht->htexpire > 0 && expires.n > 0) {
-				expires.n -= now;
-				if(ht_set_cell_expire(ht, &hname, 0, &expires)) {
-					LM_ERR("error setting expires to hash entry [%*.s]\n", hname.len, hname.s);
+				if(ht_set_cell(ht, &hname, (vtype)?0:AVP_VAL_STR, &val, mode))
+				{
+					LM_ERR("error adding to hash table\n");
 					goto error;
 				}
+
+				/* set expiry */
+				if (ht->htexpire > 0 && expires.n > 0) {
+					expires.n -= now;
+					if(ht_set_cell_expire(ht, &hname, 0, &expires)) {
+						LM_ERR("error setting expires to hash entry [%*.s]\n", hname.len, hname.s);
+						goto error;
+					}
+				}
 			}
 	 	}
 		if (DB_CAPABILITY(ht_dbf, DB_CAP_FETCH)) {