-
Notifications
You must be signed in to change notification settings - Fork 1
/
postgresffi.lua
286 lines (231 loc) · 9.33 KB
/
postgresffi.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
--------------------------------------------------------------------------
-- This module is a luajit ffi binding for the postgresql api
-- with a spacial emphasis on the non blocking functions.
--
-- Copyright (C) 2012 Moritz Kühner, Germany.
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------
PSqlConnection = {}
local PSqlConnection_mt = { __index = PSqlConnection }
local ffi = require("ffi")
local libpq = nil
--definitions of the c api
--se http://www.postgresql.org/docs/9.2/static/libpq-async.html
ffi.cdef[[
typedef struct PGconn_s PGconn;
typedef struct PGresult_s PGresult;
typedef enum
{
PGRES_POLLING_FAILED = 0,
PGRES_POLLING_READING, /* These two indicate that one may */
PGRES_POLLING_WRITING, /* use select before polling again. */
PGRES_POLLING_OK
} PostgresPollingStatusType;
typedef enum
{
PGRES_EMPTY_QUERY = 0, /* empty query string was executed */
PGRES_COMMAND_OK, /* a query command that doesn't return
* anything was executed properly by the
* backend */
PGRES_TUPLES_OK, /* a query command that returns tuples was
* executed properly by the backend, PGresult
* contains the result tuples */
PGRES_COPY_OUT, /* Copy Out data transfer in progress */
PGRES_COPY_IN, /* Copy In data transfer in progress */
PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from the
* backend */
PGRES_NONFATAL_ERROR, /* notice or warning message */
PGRES_FATAL_ERROR, /* query failed */
PGRES_COPY_BOTH, /* Copy In/Out data transfer in progress */
PGRES_SINGLE_TUPLE /* single tuple from larger resultset */
} ExecStatusType;
typedef enum
{
CONNECTION_OK,
CONNECTION_BAD,
/* Non-blocking mode only below here */
/*
* The existence of these should never be relied upon - they should only
* be used for user feedback or similar purposes.
*/
CONNECTION_STARTED, /* Waiting for connection to be made. */
CONNECTION_MADE, /* Connection OK; waiting to send. */
CONNECTION_AWAITING_RESPONSE, /* Waiting for a response from the
* postmaster. */
CONNECTION_AUTH_OK, /* Received authentication; waiting for
* backend startup. */
CONNECTION_SETENV, /* Negotiating environment. */
CONNECTION_SSL_STARTUP, /* Negotiating SSL. */
CONNECTION_NEEDED /* Internal state: connect() needed */
} ConnStatusType;
PGconn *PQconnectStart(const char *conninfo);
//Returns the status of the connection.
ConnStatusType PQstatus(const PGconn *conn);
PostgresPollingStatusType PQconnectPoll(PGconn *conn);
int PQsetnonblocking(PGconn *conn, int arg);
int PQsendQuery(PGconn *conn, const char *command);
char *PQerrorMessage(const PGconn *conn);
int PQconsumeInput(PGconn *conn);
int PQisBusy(PGconn *conn);
PGresult *PQgetResult(PGconn *conn);
ExecStatusType PQresultStatus(const PGresult *res);
//Returns the number of rows (tuples) in the query result.
int PQntuples(const PGresult *res);
//Returns the number of columns (fields) in each row of the query result.
int PQnfields(const PGresult *res);
//Returns the column name associated with the given column number. Column numbers start at 0.
//The caller should not free the result directly.
//It will be freed when the associated PGresult handle is passed to PQclear.
char *PQfname(const PGresult *res, int column_number);
//Returns a single field value of one row of a PGresult.
//Row and column numbers start at 0. The caller should not
//free the result directly. It will be freed when the associated
//PGresult handle is passed to PQclear.
char *PQgetvalue(const PGresult *res, int row_number, int column_number);
//Tests a field for a null value. Row and column numbers start at 0.
int PQgetisnull(const PGresult *res, int row_number, int column_number);
//Returns the actual length of a field value in bytes. Row and column numbers start at 0.
int PQgetlength(const PGresult *res, int row_number, int column_number);
//Returns the actual length of a field value in bytes. Row and column numbers start at 0.
int PQgetlength(const PGresult *res, int row_number, int column_number);
//PQescapeLiteral escapes a string for use within an SQL command.
//This memory should be freed using PQfreemem() when the result is no longer needed.
char *PQescapeLiteral(PGconn *conn, const char *str, size_t length);
void PQfreemem(void *ptr);
//Frees the storage associated with a PGresult.
void PQclear(PGresult *res);
//Closes the connection to the server. Also frees memory used by the PGconn object.
void PQfinish(PGconn *conn);
//returns the filedescriptor for this connection
int PQsocket(PGconn *conn);
]]
--[[Loads the shared library of postgres
libpath is the path to the library (ie. the .so or .dll) of postgres
defaults to "/usr/lib/libpq.so.5" if omitted
]]
function PSqlConnection.init(libpath)
libpq = ffi.load(libpath or "/usr/lib/libpq.so.5")
end
--[[Creates a new connection to a Postgres SQL in
async mode. The string conninfo is passed to
PQconnectStart.
]]
function PSqlConnection.newAsync(conninfo)
local new_inst = {}
setmetatable( new_inst, PSqlConnection_mt )
assert(conninfo)
new_inst.PGconn = ffi.gc(libpq.PQconnectStart(conninfo), libpq.PQfinish)
return new_inst
end
--[[Returns the dialUpState
]]
function PSqlConnection:dialUpState()
assert(libpq.PQconnectPoll(self.PGconn) ~= "PGRES_POLLING_FAILED", self:getError())
return libpq.PQstatus(self.PGconn)
end
--[[Returns the last errormessage
]]
function PSqlConnection:getError()
return ffi.string(libpq.PQerrorMessage(self.PGconn))
end
--[[Sends a query to the server
All old data has to be collecet befor an new
query can be send (getAvailable returns nil)
Node: Multiple querys in one will be treated as one transaction.
read PostgreSQL doc 31.3.1
]]
function PSqlConnection:sendQuery(query)
assert(not self.queryInProcess, "Error: Old query isn't finished")
local ret = libpq.PQsendQuery(self.PGconn, query)
if ret == 0 then
error(self:getError())
end
self.queryInProcess = true
end
--[[Returns true if getAvailable can be called without
blocking
]]
function PSqlConnection:readReady()
local ret = libpq.PQconsumeInput(self.PGconn)
if ret == 0 then
error(self:getError())
end
return libpq.PQisBusy(self.PGconn) == 0
end
--[[Returns a tables of all avilable data or nil if no more data is availible
The table is structured with the column names in row 0 and the data afterwards
ie. [1][1] is the first row in the first column and [0][1] is the name of that column
| 1 | 2 | ...
-------------------------------------------
0 | column name 1 | column name 2 | ...
--------------------------------------------
1 | data column 1 | data ......
]]
function PSqlConnection:getAvailable()
local result = libpq.PQgetResult(self.PGconn)
if result ~= nil then
local status = libpq.PQresultStatus(result)
local rows = libpq.PQntuples(result)
local columns = libpq.PQnfields(result)
local tab = {}
tab[0] = {}
for j = 1, columns do
tab[0][j] = ffi.string(libpq.PQfname(result, j-1))
end
for i = 1, rows do
tab[i] = {}
for j = 1, columns do
local value = nil
if 0 == libpq.PQgetisnull(result, i-1, j-1) then
value = ffi.string(
libpq.PQgetvalue(result, i-1, j-1),
libpq.PQgetlength(result, i-1, j-1)
)
end
tab[i][j] = value
end
end
libpq.PQclear(result)
return tab, status
else
self.queryInProcess = nil
return nil
end
end
--[[Returns the socketdescriptor
]]
function PSqlConnection:getSocket()
return libpq.PQsocket(self.PGconn)
end
--[[Returns a escaped version of the string than can be savely
used in a query without danger of SQL injection
]]
function PSqlConnection:escape(data)
local strData = tostring(data)
local result = libpq.PQescapeLiteral(self.PGconn, strData, #strData)
if result ~= nil then
local strResult = ffi.string(result)
libpq.PQfreemem(result)
return strResult
else
error(self:getError())
end
end
return PSqlConnection