├── README.md ├── darwin.makefile ├── k.c ├── kx.sh ├── linux.makefile ├── makefile ├── test.lua └── test.q /README.md: -------------------------------------------------------------------------------- 1 | # KDB client for LUA 2 | 3 | You can use this to link with nginx+lua/openresty/whatever to get a Q-powered 4 | web service that can do more than a few thousand connections per second. 5 | 6 | 7 | # Pre-Flight 8 | 9 | ## Building on OSX 10 | 11 | Linking with m64/c.o requires GCC 12 | 13 | brew install gcc lua rlwrap 14 | 15 | ## Building on Linux 16 | 17 | Default is to build lua using l64/c.o 18 | 19 | apt-get install build-essential liblua5.1-dev 20 | 21 | but kdb is 32-bit, so to get *it* working you'll need: 22 | 23 | dpkg --add-architecture i386 24 | apt-get update 25 | apt-get install -yy --no-install-recommends ia32-libs gcc rlwrap 26 | 27 | # Testing 28 | 29 | Start up kdb in a window: 30 | 31 | rlwrap ~/q/?32/q 32 | 33 | and load the server: 34 | 35 | \l test.q 36 | 37 | Run the lua-side tests in another window: 38 | 39 | make test 40 | 41 | -------------------------------------------------------------------------------- /darwin.makefile: -------------------------------------------------------------------------------- 1 | CC=gcc-9 2 | #LDFLAGS=-bundle -undefined dynamic_lookup 3 | KARCH=m64 4 | -------------------------------------------------------------------------------- /k.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "kx/k.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define RI(i) {lua_pushinteger(L,i);R 1;} 10 | #define LI(x) ((int)(floor(x)-1)) 11 | static K __thread conn_open = 0; 12 | 13 | static int enc(K*k,lua_State *L) 14 | { 15 | switch (lua_type(L, -1)) { 16 | case LUA_TSTRING: { size_t len;const char *str = lua_tolstring(L,-1,&len);(*k)=kpn((S)str,len);R 1;} break; 17 | case LUA_TNUMBER: { F num = lua_tonumber(L,-1);(*k) = (num==floor(num))?kj((J)num):kf(num);R 1;} break; 18 | case LUA_TBOOLEAN: { (*k)=kb( lua_toboolean(L,-1) );R 1;} break; 19 | case LUA_TNIL: { (*k)=ktn(0,0);R 1;} break; 20 | case LUA_TTABLE: { 21 | double p; 22 | int max = 0; 23 | int items = 0; 24 | int t_integer = 0, t_number = 0, t_boolean = 0, t_other= 0; 25 | 26 | lua_pushnil(L); 27 | /* table, startkey */ 28 | while (lua_next(L, -2) != 0) { 29 | items++; 30 | 31 | /* table, key, value */ 32 | switch (lua_type(L, -1)) { 33 | case LUA_TNUMBER: t_number++; p = lua_tonumber(L,-1); t_integer += (floor(p) == p); break; 34 | case LUA_TBOOLEAN: t_boolean++; break; 35 | default: t_other++; break; /* or anything else */ 36 | }; 37 | 38 | if (lua_type(L, -2) == LUA_TNUMBER && 39 | (p = lua_tonumber(L, -2))) { 40 | /* Integer >= 1 ? */ 41 | if (floor(p) == p && p >= 1) { 42 | if (p > max) 43 | max = p; 44 | lua_pop(L, 1); 45 | continue; 46 | } 47 | } 48 | 49 | /* Must not be an array (non integer key) */ 50 | for (lua_pop(L,1); lua_next(L, -2) != 0; lua_pop(L,1)) ++items; 51 | max = 0; 52 | break; 53 | } 54 | lua_pushnil(L); 55 | if (max != items) { 56 | /* build K dictionary */ 57 | K keys = ktn(KS,items); 58 | K values = ktn(0,items); 59 | int n = 0; 60 | /* table, startkey */ 61 | while (lua_next(L, -2) != 0) { 62 | kS(keys)[n] = ss((S)lua_tostring(L, -2)); 63 | if(!enc(kK(values)+n,L))R 0; 64 | lua_pop(L,1); 65 | ++n; 66 | } 67 | *k = xD(keys,values); 68 | R 1; 69 | } 70 | /* build K list */ 71 | if(t_other || ((!!t_boolean)+(!!t_number)) > 1) { 72 | K a = ktn(0,items); 73 | while (lua_next(L, -2) != 0) { 74 | p = lua_tonumber(L, -2); 75 | if(!enc(kK(a)+LI(p),L))R 0; 76 | lua_pop(L, 1); 77 | } 78 | *k = a; 79 | R 1; 80 | } 81 | if(t_boolean) { 82 | K a = ktn(KB,items); 83 | while (lua_next(L, -2) != 0) { 84 | p = lua_tonumber(L, -2); 85 | kG(a)[LI(p)] = lua_toboolean(L,-1); 86 | lua_pop(L, 1); 87 | } 88 | *k = a; 89 | R 1; 90 | } 91 | if(t_number == t_integer) { 92 | K a = ktn(KJ,items); 93 | while (lua_next(L, -2) != 0) { 94 | p = lua_tonumber(L, -2); 95 | kJ(a)[LI(p)] = (int)floor(lua_tonumber(L,-1)); 96 | lua_pop(L, 1); 97 | } 98 | *k = a; 99 | R 1; 100 | } 101 | if(t_number) { 102 | K a = ktn(KF,items); 103 | while (lua_next(L, -2) != 0) { 104 | p = lua_tonumber(L, -2); 105 | kF(a)[LI(p)] = lua_tonumber(L,-1); 106 | lua_pop(L, 1); 107 | } 108 | *k = a; 109 | R 1; 110 | } 111 | *k = ktn(0,0); 112 | R 1; 113 | }; break; 114 | default: 115 | luaL_error(L, "Cannot serialise %s: %s", lua_typename(L, lua_type(L, -1)), "can't serialize type"); 116 | R 0; 117 | }; 118 | } 119 | #define LD(a,b) lua_createtable(L,xn,0);DO(xn,b(L,a(x)[i]);lua_rawseti(L,-2,i)); 120 | static int dec(lua_State* L,K x) 121 | { 122 | if(xt >= 0) { 123 | switch(xt) { 124 | case 0: LD(kK,dec); break; 125 | case 1: LD(kG,lua_pushboolean);break; 126 | //case 2: /* guid */ 127 | case 4: LD(kG,lua_pushinteger);break; 128 | case 5: LD(kH,lua_pushinteger);break; 129 | case 6: LD(kI,lua_pushinteger);break; 130 | case 7: LD(kJ,lua_pushnumber);break; 131 | case 8: LD(kE,lua_pushinteger);break; 132 | case 9: LD(kF,lua_pushinteger);break; 133 | case 10:lua_pushlstring(L,kG(x),xn);break; 134 | case 11:LD(kS,lua_pushstring);break; 135 | //case 12: /* timestamp */ 136 | //case 13: /* month */ 137 | //case 14: /* date */ 138 | //case 15: /* datetime */ 139 | //case 16: /* timespan */ 140 | //case 17: /* minute */ 141 | //case 18: /* second */ 142 | //case 19: /* time */ 143 | //case 98: /* dict */ 144 | //case 99: /* table */ 145 | 146 | default:luaL_error(L, "unsupported array %d (nyi?)", xt);R 0; 147 | }; 148 | R 1; /* create table */ 149 | } 150 | switch(xt) { 151 | case -1: lua_pushboolean(L,x->g); break; 152 | //case -2: /* scalar guid */ 153 | case -4: lua_pushinteger(L,x->g); break; 154 | case -5: lua_pushinteger(L,x->h); break; 155 | case -6: lua_pushinteger(L,x->i); break; 156 | case -7: lua_pushnumber(L,x->j); break; 157 | case -8: lua_pushnumber(L,x->e); break; 158 | case -9: lua_pushnumber(L,x->f); break; 159 | case -10: lua_pushlstring(L,&x->g,1); break; 160 | case -11: lua_pushstring(L,x->s); break; 161 | case -128:luaL_error(L,"K: %s",kS(x));R 0; 162 | default:luaL_error(L, "unsupported scalar %d (nyi?)", xt);R 0; 163 | }; 164 | R 1; 165 | } 166 | 167 | static void clean(void*_) 168 | { 169 | K x = conn_open; 170 | if(x) { 171 | DO(xn, kclose(xI[i])); 172 | r0(x); 173 | } 174 | conn_open = 0; 175 | m9(); 176 | } 177 | static int conn(int fd) 178 | { 179 | if(!conn_open) { 180 | pthread_key_t rr; 181 | pthread_key_create(&rr,clean); 182 | conn_open = ktn(6,0); 183 | } 184 | conn_open = ja(&conn_open, &fd); 185 | return fd; 186 | } 187 | 188 | static int wrap_khp(lua_State* L) 189 | { 190 | S h=luaL_optstring(L,1,"0"); 191 | I p=luaL_optint(L,2,5000); 192 | S u=luaL_optstring(L,3,0); 193 | I t=luaL_optint(L,4,-1); 194 | if(t<0){if(!u)RI(conn(khp(h,p)));RI(conn(khpu(h,p,u)));} 195 | RI(conn(khpun(h,p,u,t))); 196 | } 197 | static int wrap_kclose(lua_State*L) 198 | { 199 | int c, n = lua_gettop(L); 200 | if(!n) { 201 | K x = conn_open; 202 | if(x) { 203 | DO(xn, kclose(xI[i])); 204 | xn = 0; 205 | } 206 | } else { 207 | c=luaL_optint(L,1,-1); 208 | if(c < 0) RI(0); 209 | K x=ktn(6,conn_open->n); xn=0; 210 | DO(conn_open->n,(x=((n=kI(conn_open)[i])==c)?kclose(c),c=-1,x:ja(&x,&n))); 211 | r0(conn_open); 212 | conn_open = x; 213 | if(c != -1) RI(0); 214 | } 215 | RI(1); 216 | } 217 | static int dok(int f,lua_State*L) 218 | { 219 | int c, n = lua_gettop(L); 220 | 221 | /* k [int] "kcode" [x [y [z]]] */ 222 | if (n>0 && lua_type(L, 1) == LUA_TNUMBER) { 223 | c = (int)lua_tonumber(L,1); 224 | --n; 225 | } else { 226 | if(!conn_open || !conn_open->n) RI(0); 227 | c = kI(conn_open)[conn_open->n - 1]; 228 | } 229 | /* k "kcode" [x [y [z]]] */ 230 | if(!n) {luaL_argcheck(L, 0, 1, "expected 1 argument"); R -1;} 231 | 232 | K x=0,y=0,z=0; 233 | if(n>1) { enc(&x,L);lua_pop(L,1);--n; } 234 | if(n>1) { y=x;enc(&x,L);lua_pop(L,1);--n; } 235 | if(n>1) { z=y;y=x;enc(&x,L);lua_pop(L,1);--n; } 236 | if(n>1) { luaL_argcheck(L,0,lua_gettop(L), "too many arguments (nyi?)"); r0(x);r0(y);r0(z); R -1; } 237 | const char *code = lua_tostring(L,-1); 238 | K r=k(c*f,code,x,y,z,(K)0); 239 | if(f==-1){/*async*/ lua_pushnil(L); R 1; } 240 | dec(L,r);r0(r); R 1; 241 | } 242 | static int wrap_k(lua_State*L) 243 | { 244 | R dok(1,L); 245 | } 246 | static int wrap_ks(lua_State*L) 247 | { 248 | R dok(-1,L); 249 | } 250 | 251 | int luaopen_k (lua_State *L) 252 | { 253 | static int did_open = 0; 254 | static const struct luaL_Reg _k [] = { 255 | {"khpun",wrap_khp}, {"khpu",wrap_khp}, {"khp",wrap_khp}, 256 | {"kclose",wrap_kclose}, 257 | {"k",wrap_k}, {"ks",wrap_ks}, 258 | {NULL,NULL} 259 | }; 260 | if(!did_open) { 261 | khp("",-1); 262 | setm(did_open = 1); 263 | } 264 | 265 | luaL_register(L, "k", _k); 266 | return 1; 267 | } 268 | 269 | -------------------------------------------------------------------------------- /kx.sh: -------------------------------------------------------------------------------- 1 | a="l64 l32 m32 m64" 2 | b="https://raw.githubusercontent.com/KxSystems/kdb/master" 3 | for x in $a; do 4 | mkdir -p "kx/$x" 5 | curl -o "kx/$x/c.o" "$b/$x/c.o" 6 | done 7 | curl -o "kx/k.h" "$b/c/c/k.h" 8 | -------------------------------------------------------------------------------- /linux.makefile: -------------------------------------------------------------------------------- 1 | KARCH=l64 2 | CFLAGS+=-pthread 3 | LIBS=/usr/local/openresty/luajit/lib/libluajit-5.1.so.2.1.0 4 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-DKXVER=3 -DLUA_COMPAT_MODULE=1 -fPIC -I/usr/local/include/luajit-2.0/ 3 | LDFLAGS=-shared 4 | LIBS=-L/usr/local/lib -lluajit -lpthread 5 | OUTPUT=k.so 6 | 7 | makefile=$(shell uname -s | tr A-Z a-z).makefile 8 | -include $(makefile) 9 | 10 | $(OUTPUT): k.o kx/$(KARCH)/c.o makefile $(makefile); $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUTPUT) k.o kx/$(KARCH)/c.o $(LIBS) 11 | k.o: k.c kx/k.h makefile $(makefile) 12 | kx/k.h kx/$(KARCH)/c.o makefile $(makefile):; sh kx.sh 13 | 14 | clean:; rm -f k.so 15 | test: $(OUTPUT) test.lua makefile; luajit test.lua 16 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | require "k" 2 | c=k.khp() 3 | k.ks(c,"a:4+2") 4 | k.ks(c,"{b::x}", 69) 5 | k.ks(c,"{c::x}", 69.42) 6 | k.ks(c,"{d::x}", "fishcakes") 7 | k.ks(c,"{e::x}", nil) 8 | k.ks(c,"{f::x}", {3,6,9,12}) 9 | k.ks(c,"{g::x}", {true,true,false,true}) 10 | k.ks(c,"{h::x}", {"fish",42,"cat"}); 11 | k.ks(c,"{i::x}", true); 12 | k.ks(c,"{j::x}", {foo="bar",cow="mom"}) 13 | print(k.k(c,"\"test\"")); 14 | print(k.k(c,"T")); 15 | 16 | 17 | -------------------------------------------------------------------------------- /test.q: -------------------------------------------------------------------------------- 1 | \p 5000 2 | T::prd (a~6),(b~69),(c~69.42),(d~"fishcakes"),(0=(count e)),(30=sum f),(3=sum g),(3=count h),(42=h 1),i,(2=count j) 3 | --------------------------------------------------------------------------------