Skip to content

Commit 8f27881

Browse files
committed
2 parents 6ab7da6 + 195ba95 commit 8f27881

File tree

7 files changed

+84
-37
lines changed

7 files changed

+84
-37
lines changed

__tests__/sqlSyntax.test.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const {parseSelect} = require('../iotSql/parseSql')
22
const {applySelect} = require('../iotSql/applySqlSelect')
33
const {applyWhereClause} = require('../iotSql/applySqlWhere')
4-
const {topic, timestamp, clientid, accountid} = require('../iotSql/sqlFunctions')
4+
const {topic, timestamp, clientid, accountid, encode} = require('../iotSql/sqlFunctions')
55
const {sqlParseTestData} = require('../testData')
66

77
const log = () => {}
@@ -29,7 +29,9 @@ describe('SQL parser', () => {
2929
context: {
3030
topic: (index) => topic(index, parsed.topic),
3131
clientid: () => clientid(parsed.topic),
32-
accountid: () => accountid()
32+
timestamp: () => timestamp(),
33+
accountid: () => accountid(),
34+
encode: (field, encoding) => encode(payload, field, encoding)
3335
}
3436
})).toEqual(expected.event)
3537
})

iotSql/applySqlSelect.js

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const _ = require('lodash')
22
const evalInContext = require('./eval')
33

4-
const BASE64_PLACEHOLDER = '*b64'
54
const brace = new Buffer('{')[0]
65
const bracket = new Buffer('[')[0]
76
const doubleQuote = new Buffer('"')[0]
@@ -25,11 +24,6 @@ const applySelect = ({select, payload, context}) => {
2524
let event = {}
2625
const json = maybeParseJSON(payload)
2726

28-
// if payload is Buffer initialize Buffer class from base64 string
29-
const payloadReplacement = Buffer.isBuffer(payload)
30-
? `new Buffer('${payload.toString('base64')}', 'base64')`
31-
: payload
32-
3327
// iterate over select parsed array
3428
// ex. [{alias: 'serialNumber', field: 'topic(2)'}, {field: 'state.reported.preferences.*'}]
3529
for (let part of select) {
@@ -45,9 +39,8 @@ const applySelect = ({select, payload, context}) => {
4539
}
4640
// check if field is sqlFunction
4741
} else if (Object.keys(context).some((sqlFunc) => (new RegExp(`${sqlFunc}\\((.*)\\)`).test(field)))) {
48-
let js = field.replace(BASE64_PLACEHOLDER, payloadReplacement)
4942
// execute sqlFunction
50-
event[alias || field.replace(/\(()\)/, '')] = evalInContext(js, context)
43+
event[alias || field.replace(/\(()\)/, '')] = evalInContext(field, context)
5144
} else {
5245
// event is some property on shadow
5346
let propPath = field.split('.')

iotSql/eval.js

+12-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
const evalInContext = (js, context) => {
2-
const {clientid, topic, accountid, timestamp} = context
1+
const evalInContext = (expr, context) => {
2+
let [func, fields] = expr.match(/(\w+)\((.*)\)/).slice(1, 3);
3+
fields = fields
4+
? fields.split(",").map((f) => f.trim().replace(/['"]+/g, ""))
5+
: [];
6+
37
try {
4-
return eval(js)
8+
return context[func](...fields);
59
} catch (err) {
6-
debugger
7-
console.log(`failed to evaluate: ${js}`)
8-
throw err
9-
}
10-
}
11-
12-
const encode = (data, encoding) => {
13-
if (encoding !== 'base64') {
14-
throw new Error('AWS Iot SQL encode() function only supports base64 as an encoding')
10+
debugger;
11+
console.log(`failed to evaluate: ${expr}`);
12+
throw err;
1513
}
16-
17-
return data.toString(encoding)
18-
}
19-
20-
module.exports = evalInContext
14+
};
15+
module.exports = evalInContext;

iotSql/parseSql.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
const BASE64_PLACEHOLDER = '*b64'
21
const SQL_REGEX = /^SELECT (.*) FROM '([^']+)'/
32
const SELECT_PART_REGEX = /^(.*?)(?: as (.*))?$/i
3+
const FIELDS_REGEX = /((\w+[\n\r\s]*\([^)]*\))|([^\n\r\s(,]+))([\n\r\s]+as[\n\r\s]+\w*)?/g
44
const WHERE_REGEX = /WHERE (.*)/
55

66
const parseSelect = sql => {
77
const [select, topic] = sql.match(SQL_REGEX).slice(1)
88
const [whereClause] = (sql.match(WHERE_REGEX) || []).slice(1)
99

1010
return {
11-
select: select
12-
// hack
13-
.replace("encode(*, 'base64')", BASE64_PLACEHOLDER)
14-
.split(',')
15-
.map(s => s.trim())
16-
.map(parseSelectPart),
11+
select: select.match(FIELDS_REGEX).map(parseSelectPart),
1712
topic,
1813
whereClause
1914
}

iotSql/sqlFunctions.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const _ = require('lodash')
2+
13
module.exports = {
24
topic: (index, topicUrl) => (typeof index !== 'undefined') ? topicUrl.split('/')[(index - 1)] : topicUrl,
35
clientid: (topicUrl) => {
@@ -8,5 +10,30 @@ module.exports = {
810
}
911
},
1012
timestamp: () => (new Date()).getTime(),
11-
accountid: () => process.env.AWS_ACCOUNT_ID
13+
accountid: () => process.env.AWS_ACCOUNT_ID,
14+
encode: (message, field, encoding) => {
15+
if (encoding !== "base64") {
16+
throw new Error(
17+
"AWS Iot SQL encode() function only supports base64 as an encoding"
18+
);
19+
}
20+
if (field === "*") {
21+
return Buffer.from(message).toString("base64");
22+
}
23+
24+
let payload;
25+
try {
26+
payload = JSON.parse(message);
27+
} catch (e) {
28+
console.log(e);
29+
}
30+
31+
const value = _.get(payload, field);
32+
if (!value) {
33+
throw new Error(
34+
`Failed to evaluate encode(${field}, 'base64'): Cannot find ${field} in payload`
35+
);
36+
}
37+
return Buffer.from(value.toString()).toString("base64");
38+
},
1239
}

ruleHandler.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const { fillSubstitutionTemplates } = require('./iotSql/substitutionTemplates')
99
const mqtt = require('mqtt')
1010
const mqttMatch = require('mqtt-match')
1111
const _ = require('lodash')
12-
const {topic, accountid, clientid, timestamp} = require('./iotSql/sqlFunctions')
12+
const {topic, accountid, clientid, timestamp, encode} = require('./iotSql/sqlFunctions')
1313

1414
/**
1515
* Searches serverless.yml for functions configurations.
@@ -172,7 +172,8 @@ module.exports = (slsOptions, slsService, serverless, log) => {
172172
topic: (index) => topic(index, topicUrl),
173173
clientid: () => clientid(topicUrl),
174174
timestamp: () => timestamp(),
175-
accountid: () => accountid()
175+
accountid: () => accountid(),
176+
encode: (field, encoding) => encode(message, field, encoding)
176177
}
177178
})
178179

testData.js

+34
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,39 @@ module.exports.sqlParseTestData = [
8585
clientid: 'test_client'
8686
}
8787
}
88+
},
89+
{
90+
sql: `SELECT encode(*, 'base64') as encodedPayload FROM '$aws/things/sn:123/shadow/update'`,
91+
payload: `{"state": {"reported": {"mode": "STAND_BY"}}}`,
92+
expected: {
93+
parsed: {
94+
select: [{ field: "encode(*, 'base64')", alias: "encodedPayload" }],
95+
topic: "$aws/things/sn:123/shadow/update",
96+
},
97+
whereEvaluatesTo: true,
98+
event: {
99+
encodedPayload:
100+
"eyJzdGF0ZSI6IHsicmVwb3J0ZWQiOiB7Im1vZGUiOiAiU1RBTkRfQlkifX19",
101+
},
102+
},
103+
},
104+
{
105+
sql: `SELECT encode(state.reported.mode, 'base64') as encodedPayload FROM '$aws/things/sn:123/shadow/update'`,
106+
payload: `{"state": {"reported": {"mode": "STAND_BY"}}}`,
107+
expected: {
108+
parsed: {
109+
select: [
110+
{
111+
field: "encode(state.reported.mode, 'base64')",
112+
alias: "encodedPayload",
113+
},
114+
],
115+
topic: "$aws/things/sn:123/shadow/update",
116+
},
117+
whereEvaluatesTo: true,
118+
event: {
119+
encodedPayload: "U1RBTkRfQlk=",
120+
},
121+
},
88122
}
89123
]

0 commit comments

Comments
 (0)