Skip to content

Commit 5e642bf

Browse files
committed
add support for unix sockets with lua posix (or ngx cosocket) via socket_path, fixes #76
1 parent b92ef6e commit 5e642bf

File tree

6 files changed

+295
-7
lines changed

6 files changed

+295
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ Available options:
139139
* `"ssl"`: enable ssl (default: `false`)
140140
* `"ssl_verify"`: verify server certificate (default: `nil`)
141141
* `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`)
142-
* `"socket_type"`: the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise)
142+
* `"socket_type"`: the type of socket to use, one of: `"nginx"`, `"luasocket"`, `"cqueues"` (default: `"nginx"` if in nginx, `"luasocket"` otherwise)
143+
* `"socket_path"`: path to Unix socket (e.g. `"/var/run/postgresql/.s.PGSQL.5432"`); when set `host` and `port` are ignored. In nginx, connects using `unix:/path` syntax; otherwise uses luaposix
143144
* `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`)
144145
* `"pool_name"`: (OpenResty only) name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}:#{user}"`)
145146
* `"pool_size"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)

pgmoon/init.lua

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ do
145145
user = "postgres",
146146
host = "127.0.0.1",
147147
port = "5432",
148-
ssl = false
148+
ssl = false,
149+
socket_path = nil
149150
},
150151
type_serializers = {
151152
string = function(self, v)
@@ -258,7 +259,16 @@ do
258259
backlog = self.config.backlog
259260
}
260261
end
261-
local ok, err = self.sock:connect(self.config.host, self.config.port, connect_opts)
262+
local ok, err
263+
if self.config.socket_path then
264+
if self.sock_type == "nginx" then
265+
ok, err = self.sock:connect("unix:" .. tostring(self.config.socket_path), connect_opts)
266+
else
267+
ok, err = self.sock:connect(self.config.socket_path)
268+
end
269+
else
270+
ok, err = self.sock:connect(self.config.host, self.config.port, connect_opts)
271+
end
262272
if not (ok) then
263273
self.busy = false
264274
return nil, err
@@ -1132,8 +1142,18 @@ do
11321142
end
11331143
})
11341144
self.convert_null = self.config.convert_null
1135-
self.sock, self.sock_type = socket.new(self.config.socket_type)
11361145
self.busy = false
1146+
local socket_type
1147+
if self.config.socket_path then
1148+
if ngx and ngx.get_phase() ~= "init" then
1149+
socket_type = "nginx"
1150+
else
1151+
socket_type = "luaposix"
1152+
end
1153+
else
1154+
socket_type = self.config.socket_type
1155+
end
1156+
self.sock, self.sock_type = socket.new(socket_type)
11371157
end,
11381158
__base = _base_0,
11391159
__name = "Postgres"

pgmoon/init.moon

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class Postgres
139139
host: "127.0.0.1"
140140
port: "5432"
141141
ssl: false
142+
socket_path: nil
142143
}
143144

144145
-- convert a lua value to pg_type.oid, string representation used for sending
@@ -248,9 +249,20 @@ class Postgres
248249
}
249250

250251
@convert_null = @config.convert_null
251-
@sock, @sock_type = socket.new @config.socket_type
252252
@busy = false
253253

254+
-- Auto-select socket type based on configuration
255+
-- If socket_path is provided, use nginx cosocket when available, otherwise luaposix
256+
socket_type = if @config.socket_path
257+
if ngx and ngx.get_phase! != "init"
258+
"nginx"
259+
else
260+
"luaposix"
261+
else
262+
@config.socket_type
263+
264+
@sock, @sock_type = socket.new socket_type
265+
254266
connect: =>
255267
error "pgmoon: connection is busy" if @busy
256268
@busy = true
@@ -263,7 +275,14 @@ class Postgres
263275
backlog: @config.backlog
264276
}
265277

266-
ok, err = @sock\connect @config.host, @config.port, connect_opts
278+
-- Handle Unix socket vs TCP connection
279+
ok, err = if @config.socket_path
280+
if @sock_type == "nginx"
281+
@sock\connect "unix:#{@config.socket_path}", connect_opts
282+
else
283+
@sock\connect @config.socket_path
284+
else
285+
@sock\connect @config.host, @config.port, connect_opts
267286
unless ok
268287
@busy = false
269288
return nil, err
@@ -1069,4 +1088,3 @@ class Postgres
10691088
"<Postgres socket: #{@sock}>"
10701089
10711090
{ :Postgres, new: Postgres, :VERSION }
1072-

pgmoon/socket.lua

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,94 @@
1+
local create_luaposix_socket
2+
do
3+
local flatten
4+
flatten = require("pgmoon.util").flatten
5+
local proxy_mt = {
6+
__index = function(self, key)
7+
local sock = self.sock
8+
local original = sock[key]
9+
if type(original) == "function" then
10+
local fn
11+
fn = function(_, ...)
12+
return original(sock, ...)
13+
end
14+
self[key] = fn
15+
return fn
16+
else
17+
return original
18+
end
19+
end
20+
}
21+
local method_overrides
22+
method_overrides = {
23+
connect = function(self, socket_path)
24+
local posix_socket = require("posix.sys.socket")
25+
local addr = {
26+
family = posix_socket.AF_UNIX,
27+
path = socket_path
28+
}
29+
local result, err, _ = posix_socket.connect(self.sock.fd, addr)
30+
if result then
31+
return true
32+
else
33+
return nil, err
34+
end
35+
end,
36+
send = function(self, ...)
37+
local posix_socket = require("posix.sys.socket")
38+
local data = flatten(...)
39+
local result, err, _ = posix_socket.send(self.sock.fd, data)
40+
if result then
41+
return #data, nil
42+
else
43+
return nil, err
44+
end
45+
end,
46+
receive = function(self, bytes)
47+
local posix_socket = require("posix.sys.socket")
48+
local result, err, _ = posix_socket.recv(self.sock.fd, bytes)
49+
if result then
50+
return result
51+
else
52+
return nil, err
53+
end
54+
end,
55+
settimeout = function(self, t)
56+
self.timeout = t
57+
end,
58+
close = function(self)
59+
local posix_unistd = require("posix.unistd")
60+
return posix_unistd.close(self.sock.fd)
61+
end,
62+
setkeepalive = function(self)
63+
return error("You attempted to call setkeepalive on a Unix socket. This method is only available for the ngx cosocket API for releasing a socket back into the connection pool")
64+
end,
65+
getreusedtimes = function(self, t)
66+
return 0
67+
end,
68+
sslhandshake = function(self, opts)
69+
if opts == nil then
70+
opts = { }
71+
end
72+
return error("SSL handshake is not supported over Unix domain sockets")
73+
end
74+
}
75+
create_luaposix_socket = function(...)
76+
local posix_socket = require("posix.sys.socket")
77+
local sockfd, err, _ = posix_socket.socket(posix_socket.AF_UNIX, posix_socket.SOCK_STREAM, 0)
78+
if not (sockfd) then
79+
error("Failed to create Unix socket: " .. tostring(err))
80+
end
81+
local proxy = {
82+
sock = {
83+
fd = sockfd
84+
}
85+
}
86+
for k, v in pairs(method_overrides) do
87+
proxy[k] = v
88+
end
89+
return setmetatable(proxy, proxy_mt)
90+
end
91+
end
192
local create_luasocket
293
do
394
local flatten
@@ -85,6 +176,7 @@ do
85176
end
86177
return {
87178
create_luasocket = create_luasocket,
179+
create_luaposix_socket = create_luaposix_socket,
88180
new = function(socket_type)
89181
if socket_type == nil then
90182
if ngx and ngx.get_phase() ~= "init" then
@@ -99,6 +191,8 @@ return {
99191
socket = ngx.socket.tcp()
100192
elseif "luasocket" == _exp_0 then
101193
socket = create_luasocket()
194+
elseif "luaposix" == _exp_0 then
195+
socket = create_luaposix_socket()
102196
elseif "cqueues" == _exp_0 then
103197
socket = require("pgmoon.cqueues").CqueuesSocket()
104198
else

pgmoon/socket.moon

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,84 @@
11

2+
-- creates a luaposix unix socket proxy to make it behave like ngx.socket.tcp
3+
create_luaposix_socket = do
4+
import flatten from require "pgmoon.util"
5+
6+
proxy_mt = {
7+
__index: (key) =>
8+
sock = @sock
9+
original = sock[key]
10+
if type(original) == "function"
11+
fn = (_, ...) ->
12+
original sock, ...
13+
@[key] = fn
14+
fn
15+
else
16+
original
17+
}
18+
19+
-- these methods are overidden from the default socket implementation
20+
-- all other methods/properties are carried over via the __index metamethod above
21+
local method_overrides
22+
method_overrides = {
23+
connect: (socket_path) =>
24+
posix_socket = require "posix.sys.socket"
25+
addr = { family: posix_socket.AF_UNIX, path: socket_path }
26+
result, err, _ = posix_socket.connect @sock.fd, addr
27+
if result
28+
true
29+
else
30+
nil, err
31+
32+
send: (...) =>
33+
posix_socket = require "posix.sys.socket"
34+
data = flatten ...
35+
result, err, _ = posix_socket.send @sock.fd, data
36+
if result
37+
#data, nil
38+
else
39+
nil, err
40+
41+
receive: (bytes) =>
42+
posix_socket = require "posix.sys.socket"
43+
result, err, _ = posix_socket.recv @sock.fd, bytes
44+
if result
45+
result
46+
else
47+
nil, err
48+
49+
settimeout: (t) =>
50+
-- Unix sockets don't have built-in timeout, but we store it for compatibility
51+
@timeout = t
52+
53+
close: =>
54+
posix_unistd = require "posix.unistd"
55+
posix_unistd.close @sock.fd
56+
57+
setkeepalive: =>
58+
error "You attempted to call setkeepalive on a Unix socket. This method is only available for the ngx cosocket API for releasing a socket back into the connection pool"
59+
60+
getreusedtimes: (t) => 0
61+
62+
sslhandshake: (opts={}) =>
63+
error "SSL handshake is not supported over Unix domain sockets"
64+
}
65+
66+
(...) ->
67+
posix_socket = require "posix.sys.socket"
68+
sockfd, err, _ = posix_socket.socket posix_socket.AF_UNIX, posix_socket.SOCK_STREAM, 0
69+
70+
unless sockfd
71+
error "Failed to create Unix socket: #{err}"
72+
73+
proxy = {
74+
sock: { fd: sockfd }
75+
}
76+
77+
for k,v in pairs method_overrides
78+
proxy[k] = v
79+
80+
setmetatable proxy, proxy_mt
81+
282
-- creates a luasocket socket proxy to make it behave like ngx.socket.tcp
383
create_luasocket = do
484
import flatten from require "pgmoon.util"
@@ -76,6 +156,7 @@ create_luasocket = do
76156

77157
{
78158
:create_luasocket
159+
:create_luaposix_socket
79160

80161
new: (socket_type) ->
81162
if socket_type == nil
@@ -93,6 +174,8 @@ create_luasocket = do
93174
ngx.socket.tcp!
94175
when "luasocket"
95176
create_luasocket!
177+
when "luaposix"
178+
create_luaposix_socket!
96179
when "cqueues"
97180
require("pgmoon.cqueues").CqueuesSocket!
98181
else

spec/pgmoon_unix_spec.moon

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Postgres from require "pgmoon"
2+
3+
SOCKET_PATH = "/var/run/postgresql/.s.PGSQL.5432"
4+
5+
-- Basic test to verify Unix socket support loads correctly
6+
describe "Unix socket support", ->
7+
it "should choose nginx or luaposix when socket_path is provided", ->
8+
pg = Postgres {
9+
socket_path: "/tmp/test.sock"
10+
user: "postgres"
11+
database: "test"
12+
}
13+
14+
if ngx and ngx.get_phase! != "init"
15+
assert.equal "nginx", pg.sock_type
16+
else
17+
assert.equal "luaposix", pg.sock_type
18+
19+
it "should accept socket_path in configuration", ->
20+
pg = Postgres {
21+
socket_path: SOCKET_PATH
22+
user: "postgres"
23+
database: "test"
24+
}
25+
26+
assert.equal SOCKET_PATH, pg.config.socket_path
27+
28+
it "should default to luasocket when no socket_path is provided", ->
29+
pg = Postgres {
30+
user: "postgres"
31+
database: "test"
32+
}
33+
34+
-- Should not be luaposix since no socket_path provided
35+
assert.is_not.equal "luaposix", pg.sock_type
36+
37+
it "should have socket_path as nil in default config", ->
38+
pg = Postgres {}
39+
assert.is_nil pg.config.socket_path
40+
41+
it "should override socket_type when socket_path is provided", ->
42+
-- Even if we explicitly set socket_type to something else,
43+
-- providing socket_path should force luaposix
44+
pg = Postgres {
45+
socket_type: "luasocket"
46+
socket_path: "/tmp/test.sock"
47+
user: "postgres"
48+
database: "test"
49+
}
50+
51+
if ngx and ngx.get_phase! != "init"
52+
assert.equal "nginx", pg.sock_type
53+
else
54+
assert.equal "luaposix", pg.sock_type
55+
56+
-- This test would require an actual Unix socket PostgreSQL server
57+
-- Keeping it commented out for now
58+
it "should connect via Unix socket", ->
59+
pg = Postgres {
60+
socket_path: SOCKET_PATH
61+
user: "postgres"
62+
database: "postgres"
63+
}
64+
65+
success, err = pg\connect!
66+
assert success, "Failed to connect: #{err}"
67+
68+
result, err = pg\query "SELECT 1 as test"
69+
assert result, "Failed to query: #{err}"
70+
assert.equal 1, result[1].test
71+
72+
pg\disconnect!

0 commit comments

Comments
 (0)