summaryrefslogtreecommitdiffstats
path: root/lualib/rspamadm/fuzzy_convert.lua
blob: fab399518f092b9149a8ba9e45f192d2580af113 (plain)
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
local sqlite3 = require "rspamd_sqlite3"
local redis = require "rspamd_redis"
local util = require "rspamd_util"

local function connect_redis(server, username, password, db)
  local ret
  local conn, err = redis.connect_sync({
    host = server,
  })

  if not conn then
    return nil, 'Cannot connect: ' .. err
  end

  if username then
    if password then
      ret = conn:add_cmd('AUTH', { username, password })
      if not ret then
        return nil, 'Cannot queue command'
      end
    else
      return nil, 'Redis requires a password when username is supplied'
    end
  elseif password then
    ret = conn:add_cmd('AUTH', { password })
    if not ret then
      return nil, 'Cannot queue command'
    end
  end
  if db then
    ret = conn:add_cmd('SELECT', { db })
    if not ret then
      return nil, 'Cannot queue command'
    end
  end

  return conn, nil
end

local function send_digests(digests, redis_host, redis_username, redis_password, redis_db)
  local conn, err = connect_redis(redis_host, redis_username, redis_password, redis_db)
  if err then
    print(err)
    return false
  end
  local ret
  for _, v in ipairs(digests) do
    ret = conn:add_cmd('HMSET', {
      'fuzzy' .. v[1],
      'F', v[2],
      'V', v[3],
    })
    if not ret then
      print('Cannot batch command')
      return false
    end
    ret = conn:add_cmd('EXPIRE', {
      'fuzzy' .. v[1],
      tostring(v[4]),
    })
    if not ret then
      print('Cannot batch command')
      return false
    end
  end
  ret, err = conn:exec()
  if not ret then
    print('Cannot execute batched commands: ' .. err)
    return false
  end
  return true
end

local function send_shingles(shingles, redis_host, redis_username, redis_password, redis_db)
  local conn, err = connect_redis(redis_host, redis_username, redis_password, redis_db)
  if err then
    print("Redis error: " .. err)
    return false
  end
  local ret
  for _, v in ipairs(shingles) do
    ret = conn:add_cmd('SET', {
      'fuzzy_' .. v[2] .. '_' .. v[1],
      v[4],
    })
    if not ret then
      print('Cannot batch SET command: ' .. err)
      return false
    end
    ret = conn:add_cmd('EXPIRE', {
      'fuzzy_' .. v[2] .. '_' .. v[1],
      tostring(v[3]),
    })
    if not ret then
      print('Cannot batch command')
      return false
    end
  end
  ret, err = conn:exec()
  if not ret then
    print('Cannot execute batched commands: ' .. err)
    return false
  end
  return true
end

local function update_counters(total, redis_host, redis_username, redis_password, redis_db)
  local conn, err = connect_redis(redis_host, redis_username, redis_password, redis_db)
  if err then
    print(err)
    return false
  end
  local ret
  ret = conn:add_cmd('SET', {
    'fuzzylocal',
    total,
  })
  if not ret then
    print('Cannot batch command')
    return false
  end
  ret = conn:add_cmd('SET', {
    'fuzzy_count',
    total,
  })
  if not ret then
    print('Cannot batch command')
    return false
  end
  ret, err = conn:exec()
  if not ret then
    print('Cannot execute batched commands: ' .. err)
    return false
  end
  return true
end

return function(_, res)
  local db = sqlite3.open(res['source_db'])
  local shingles = {}
  local digests = {}
  local num_batch_digests = 0
  local num_batch_shingles = 0
  local total_digests = 0
  local total_shingles = 0
  local lim_batch = 1000 -- Update each 1000 entries
  local redis_username = res['redis_username']
  local redis_password = res['redis_password']
  local redis_db = nil

  if res['redis_db'] then
    redis_db = tostring(res['redis_db'])
  end

  if not db then
    print('Cannot open source db: ' .. res['source_db'])
    return
  end

  local now = util.get_time()
  for row in db:rows('SELECT id, flag, digest, value, time FROM digests') do

    local expire_in = math.floor(now - row.time + res['expiry'])
    if expire_in >= 1 then
      table.insert(digests, { row.digest, row.flag, row.value, expire_in })
      num_batch_digests = num_batch_digests + 1
      total_digests = total_digests + 1
      for srow in db:rows('SELECT value, number FROM shingles WHERE digest_id = ' .. row.id) do
        table.insert(shingles, { srow.value, srow.number, expire_in, row.digest })
        total_shingles = total_shingles + 1
        num_batch_shingles = num_batch_shingles + 1
      end
    end
    if num_batch_digests >= lim_batch then
      if not send_digests(digests, res['redis_host'], redis_username, redis_password, redis_db) then
        return
      end
      num_batch_digests = 0
      digests = {}
    end
    if num_batch_shingles >= lim_batch then
      if not send_shingles(shingles, res['redis_host'], redis_username, redis_password, redis_db) then
        return
      end
      num_batch_shingles = 0
      shingles = {}
    end
  end
  if digests[1] then
    if not send_digests(digests, res['redis_host'], redis_username, redis_password, redis_db) then
      return
    end
  end
  if shingles[1] then
    if not send_shingles(shingles, res['redis_host'], redis_username, redis_password, redis_db) then
      return
    end
  end

  local message = string.format(
      'Migrated %d digests and %d shingles',
      total_digests, total_shingles
  )
  if not update_counters(total_digests, res['redis_host'], redis_username, redis_password, redis_db) then
    message = message .. ' but failed to update counters'
  end
  print(message)
end