skylight 0.1.8 → 0.2.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/skylight.rb +1 -0
- data/lib/skylight/config.rb +1 -1
- data/lib/skylight/instrumenter.rb +53 -11
- data/lib/skylight/messages.rb +1 -0
- data/lib/skylight/messages/error.rb +10 -0
- data/lib/skylight/messages/span.rb +78 -8
- data/lib/skylight/messages/trace.rb +7 -3
- data/lib/skylight/normalizers/process_action.rb +12 -1
- data/lib/skylight/normalizers/render_partial.rb +1 -1
- data/lib/skylight/normalizers/render_template.rb +1 -1
- data/lib/skylight/normalizers/sql.rb +32 -6
- data/lib/skylight/railtie.rb +5 -1
- data/lib/skylight/subscriber.rb +21 -7
- data/lib/skylight/version.rb +1 -1
- data/lib/skylight/worker/collector.rb +27 -3
- data/lib/skylight/worker/server.rb +2 -3
- data/lib/sql_lexer.rb +6 -0
- data/lib/sql_lexer/lexer.rb +259 -0
- data/lib/sql_lexer/version.rb +3 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b64f9d0eb45e48301e2b937366229e63bc339af3
|
4
|
+
data.tar.gz: 39656e4d86ac0d4f3496101643105fba58fdf4a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fe73155548f5bca71d6fab4049809fe354c8c6b205f9e26001595dd6454920a3c3fdbcab7b224a6069336ab25c3d38090f19133ba4665b523d63ca120ca0b6f
|
7
|
+
data.tar.gz: f6f4ab23ae9c2771d2eb702725e54dce515885319e13b442e02db549af99a349014cb45b17e370125e79b2f412005cd239e919c4ab266839cba9ab8aca49e1ce
|
data/lib/skylight.rb
CHANGED
data/lib/skylight/config.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
module Skylight
|
4
5
|
class Instrumenter
|
5
6
|
KEY = :__skylight_current_trace
|
6
7
|
LOCK = Mutex.new
|
8
|
+
DESC_LOCK = Mutex.new
|
9
|
+
|
10
|
+
TOO_MANY_UNIQUES = "<too many unique descriptions>"
|
7
11
|
|
8
12
|
include Util::Logging
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
class TraceInfo
|
15
|
+
def current
|
16
|
+
Thread.current[KEY]
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
19
|
+
def current=(trace)
|
20
|
+
Thread.current[KEY] = trace
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
18
24
|
def self.instance
|
@@ -36,7 +42,7 @@ module Skylight
|
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
|
-
attr_reader :config, :gc
|
45
|
+
attr_reader :config, :gc, :trace_info
|
40
46
|
|
41
47
|
def initialize(config)
|
42
48
|
if Hash === config
|
@@ -46,7 +52,18 @@ module Skylight
|
|
46
52
|
@gc = config.gc
|
47
53
|
@config = config
|
48
54
|
@worker = config.worker.build
|
49
|
-
@subscriber = Subscriber.new(config)
|
55
|
+
@subscriber = Subscriber.new(config, self)
|
56
|
+
|
57
|
+
@trace_info = @config[:trace_info] || TraceInfo.new
|
58
|
+
@descriptions = Hash.new { |h,k| h[k] = Set.new }
|
59
|
+
end
|
60
|
+
|
61
|
+
def current_trace
|
62
|
+
@trace_info.current
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_trace=(trace)
|
66
|
+
@trace_info.current = trace
|
50
67
|
end
|
51
68
|
|
52
69
|
def start!
|
@@ -72,7 +89,7 @@ module Skylight
|
|
72
89
|
|
73
90
|
def trace(endpoint, cat, *args)
|
74
91
|
# If a trace is already in progress, continue with that one
|
75
|
-
if trace =
|
92
|
+
if trace = @trace_info.current
|
76
93
|
t { "already tracing" }
|
77
94
|
return yield(trace) if block_given?
|
78
95
|
return trace
|
@@ -86,20 +103,20 @@ module Skylight
|
|
86
103
|
return
|
87
104
|
end
|
88
105
|
|
89
|
-
|
106
|
+
@trace_info.current = trace
|
90
107
|
return trace unless block_given?
|
91
108
|
|
92
109
|
begin
|
93
110
|
yield trace
|
94
111
|
|
95
112
|
ensure
|
96
|
-
|
113
|
+
@trace_info.current = nil
|
97
114
|
trace.submit
|
98
115
|
end
|
99
116
|
end
|
100
117
|
|
101
118
|
def instrument(cat, *args)
|
102
|
-
unless trace =
|
119
|
+
unless trace = @trace_info.current
|
103
120
|
return yield if block_given?
|
104
121
|
return
|
105
122
|
end
|
@@ -128,6 +145,31 @@ module Skylight
|
|
128
145
|
end
|
129
146
|
end
|
130
147
|
|
148
|
+
def limited_description(description)
|
149
|
+
endpoint = @trace_info.current.endpoint
|
150
|
+
|
151
|
+
DESC_LOCK.synchronize do
|
152
|
+
set = @descriptions[endpoint]
|
153
|
+
|
154
|
+
if set.size >= 100
|
155
|
+
return TOO_MANY_UNIQUES if set.size >= 100
|
156
|
+
end
|
157
|
+
|
158
|
+
set << description
|
159
|
+
description
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def error(reason, body)
|
164
|
+
t { fmt "processing error; reason=%s; body=%s", reason, body }
|
165
|
+
|
166
|
+
message = Skylight::Messages::Error.new(reason: reason, body: body)
|
167
|
+
|
168
|
+
unless @worker.submit(message)
|
169
|
+
warn "failed to submit error to worker"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
131
173
|
def process(trace)
|
132
174
|
t { fmt "processing trace; spans=%d; duration=%d",
|
133
175
|
trace.spans.length, trace.spans[-1].duration }
|
data/lib/skylight/messages.rb
CHANGED
@@ -1,5 +1,71 @@
|
|
1
1
|
module Skylight
|
2
2
|
module Messages
|
3
|
+
class AnnotationBuilder
|
4
|
+
def self.build(object)
|
5
|
+
new(object).build
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(object)
|
9
|
+
@object = object
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
build_nested(@object)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def build_nested(object)
|
18
|
+
case object
|
19
|
+
when Hash
|
20
|
+
build_hash(object)
|
21
|
+
when Array
|
22
|
+
build_array(object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_hash(object)
|
27
|
+
object.map do |key, value|
|
28
|
+
build_annotation(value, key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_array(array)
|
33
|
+
array.map do |value|
|
34
|
+
build_annotation(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_annotation(value, key=nil)
|
39
|
+
Annotation.new.tap do |annotation|
|
40
|
+
annotation.key = key.to_s if key
|
41
|
+
annotation[classify(value)] = build_value(value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_value(value)
|
46
|
+
nested?(value) ? build_nested(value) : value
|
47
|
+
end
|
48
|
+
|
49
|
+
def nested?(value)
|
50
|
+
value.is_a?(Array) || value.is_a?(Hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def classify(value)
|
54
|
+
case value
|
55
|
+
when String
|
56
|
+
:string
|
57
|
+
when Integer
|
58
|
+
:int
|
59
|
+
when Numeric
|
60
|
+
:double
|
61
|
+
when Hash, Array
|
62
|
+
:nested
|
63
|
+
else
|
64
|
+
raise Skylight::SerializeError.new("Annotation values must be Strings or Numeric. You passed #{value.inspect} in #{@object.inspect}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
3
69
|
class Span
|
4
70
|
include Beefcake::Message
|
5
71
|
|
@@ -32,13 +98,15 @@ module Skylight
|
|
32
98
|
attr_accessor :children
|
33
99
|
|
34
100
|
def initialize(trace, time, started_at, cat, title, desc, annot)
|
35
|
-
@trace
|
36
|
-
@built
|
37
|
-
@time
|
38
|
-
@started_at
|
39
|
-
@category
|
40
|
-
@children
|
41
|
-
|
101
|
+
@trace = trace
|
102
|
+
@built = false
|
103
|
+
@time = time
|
104
|
+
@started_at = started_at
|
105
|
+
@category = cat.to_s
|
106
|
+
@children = 0
|
107
|
+
@annotations = annot
|
108
|
+
|
109
|
+
self.title = title
|
42
110
|
self.description = desc
|
43
111
|
end
|
44
112
|
|
@@ -87,7 +155,9 @@ module Skylight
|
|
87
155
|
private
|
88
156
|
|
89
157
|
def to_annotations(val)
|
90
|
-
|
158
|
+
return nil if !val || val.empty?
|
159
|
+
|
160
|
+
AnnotationBuilder.build(val)
|
91
161
|
end
|
92
162
|
|
93
163
|
end
|
@@ -51,6 +51,8 @@ module Skylight
|
|
51
51
|
desc = args.shift
|
52
52
|
now = adjust_for_skew(Util::Clock.micros)
|
53
53
|
|
54
|
+
desc = @instrumenter.limited_description(desc)
|
55
|
+
|
54
56
|
sp = span(now - gc_time, cat, title, desc, annot)
|
55
57
|
inc_children
|
56
58
|
@spans << sp.build(0)
|
@@ -59,11 +61,13 @@ module Skylight
|
|
59
61
|
end
|
60
62
|
|
61
63
|
def instrument(cat, *args)
|
62
|
-
annot = args.pop if Hash === args
|
64
|
+
annot = args.pop if Hash === args.last
|
63
65
|
title = args.shift
|
64
66
|
desc = args.shift
|
65
67
|
now = adjust_for_skew(Util::Clock.micros)
|
66
68
|
|
69
|
+
desc = @instrumenter.limited_description(desc)
|
70
|
+
|
67
71
|
start(now - gc_time, cat, title, desc, annot)
|
68
72
|
end
|
69
73
|
|
@@ -73,8 +77,8 @@ module Skylight
|
|
73
77
|
end
|
74
78
|
|
75
79
|
def release
|
76
|
-
return unless
|
77
|
-
|
80
|
+
return unless @instrumenter.current_trace == self
|
81
|
+
@instrumenter.current_trace = nil
|
78
82
|
end
|
79
83
|
|
80
84
|
def submit
|
@@ -5,7 +5,7 @@ module Skylight
|
|
5
5
|
|
6
6
|
def normalize(trace, name, payload)
|
7
7
|
trace.endpoint = controller_action(payload)
|
8
|
-
[ "app.controller.request", trace.endpoint, nil, payload ]
|
8
|
+
[ "app.controller.request", trace.endpoint, nil, normalize_payload(payload) ]
|
9
9
|
end
|
10
10
|
|
11
11
|
private
|
@@ -13,6 +13,17 @@ module Skylight
|
|
13
13
|
def controller_action(payload)
|
14
14
|
"#{payload[:controller]}##{payload[:action]}"
|
15
15
|
end
|
16
|
+
|
17
|
+
def normalize_payload(payload)
|
18
|
+
normalized = {}
|
19
|
+
|
20
|
+
payload.each do |key, value|
|
21
|
+
value = value.inspect unless value.is_a?(String) || value.is_a?(Numeric)
|
22
|
+
normalized[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
normalized
|
26
|
+
end
|
16
27
|
end
|
17
28
|
end
|
18
29
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require "sql_lexer"
|
2
|
+
require "json"
|
3
|
+
|
1
4
|
module Skylight
|
2
5
|
module Normalizers
|
3
6
|
class SQL < Normalizer
|
@@ -9,16 +12,39 @@ module Skylight
|
|
9
12
|
return :skip
|
10
13
|
else
|
11
14
|
name = "db.sql.query"
|
12
|
-
title = payload[:name]
|
15
|
+
title = payload[:name] || "SQL"
|
13
16
|
end
|
14
17
|
|
15
|
-
|
18
|
+
if payload[:binds].empty?
|
19
|
+
extracted_title, payload[:sql], binds, error = extract_binds(payload)
|
20
|
+
else
|
21
|
+
extracted_title, _, _, error = extract_binds(payload)
|
22
|
+
binds = payload[:binds].map { |col, val| val.inspect }
|
23
|
+
end
|
24
|
+
|
25
|
+
title = extracted_title if extracted_title
|
26
|
+
|
16
27
|
|
17
|
-
|
18
|
-
|
19
|
-
|
28
|
+
if payload[:sql]
|
29
|
+
annotations = {
|
30
|
+
sql: payload[:sql],
|
31
|
+
binds: binds,
|
32
|
+
}
|
33
|
+
else
|
34
|
+
annotations = {
|
35
|
+
skylight_error: error
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
[ name, title, payload[:sql], annotations ]
|
40
|
+
end
|
20
41
|
|
21
|
-
|
42
|
+
private
|
43
|
+
def extract_binds(payload)
|
44
|
+
title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql])
|
45
|
+
[ title, sql, binds, nil ]
|
46
|
+
rescue
|
47
|
+
[ nil, nil, nil, ["sql_parse", payload[:sql]] ]
|
22
48
|
end
|
23
49
|
end
|
24
50
|
end
|
data/lib/skylight/railtie.rb
CHANGED
@@ -24,6 +24,10 @@ module Skylight
|
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
+
def existent_paths(paths)
|
28
|
+
paths.respond_to?(:existent) ? paths.existent : paths.select { |f| File.exists?(f) }
|
29
|
+
end
|
30
|
+
|
27
31
|
def load_skylight_config(app)
|
28
32
|
path = config_path(app)
|
29
33
|
path = nil unless File.exist?(path)
|
@@ -39,7 +43,7 @@ module Skylight
|
|
39
43
|
configure_logging(config, app)
|
40
44
|
|
41
45
|
config['agent.sockfile_path'] = tmp
|
42
|
-
config['normalizers.render.view_paths'] = app.config.paths["app/views"]
|
46
|
+
config['normalizers.render.view_paths'] = existent_paths(app.config.paths["app/views"])
|
43
47
|
config.validate!
|
44
48
|
config
|
45
49
|
|
data/lib/skylight/subscriber.rb
CHANGED
@@ -4,10 +4,11 @@ module Skylight
|
|
4
4
|
|
5
5
|
attr_reader :config
|
6
6
|
|
7
|
-
def initialize(config)
|
8
|
-
@config
|
9
|
-
@subscriber
|
10
|
-
@normalizers
|
7
|
+
def initialize(config, instrumenter)
|
8
|
+
@config = config
|
9
|
+
@subscriber = nil
|
10
|
+
@normalizers = Normalizers.build(config)
|
11
|
+
@instrumenter = instrumenter
|
11
12
|
end
|
12
13
|
|
13
14
|
def register!
|
@@ -35,23 +36,32 @@ module Skylight
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def start(name, id, payload)
|
38
|
-
return unless trace =
|
39
|
+
return unless trace = @instrumenter.current_trace
|
39
40
|
|
40
41
|
cat, title, desc, annot = normalize(trace, name, payload)
|
41
42
|
|
43
|
+
if cat != :skip && error = annot[:skylight_error]
|
44
|
+
@instrumenter.error(*error)
|
45
|
+
end
|
46
|
+
|
42
47
|
unless cat == :skip
|
43
48
|
span = trace.instrument(cat, title, desc, annot)
|
44
49
|
end
|
45
50
|
|
46
51
|
trace.notifications << Notification.new(name, span)
|
47
|
-
|
48
52
|
rescue Exception => e
|
49
53
|
error "Subscriber#start error; msg=%s", e.message
|
54
|
+
debug "trace=%s", trace.inspect
|
55
|
+
debug "in: name=%s", name.inspect
|
56
|
+
debug "in: payload=%s", payload.inspect
|
57
|
+
debug "out: cat=%s, title=%s, desc=%s", cat.inspect, name.inspect, desc.inspect
|
58
|
+
debug "out: annot=%s", annot.inspect
|
59
|
+
t { e.backtrace.join("\n") }
|
50
60
|
nil
|
51
61
|
end
|
52
62
|
|
53
63
|
def finish(name, id, payload)
|
54
|
-
return unless trace =
|
64
|
+
return unless trace = @instrumenter.current_trace
|
55
65
|
|
56
66
|
while curr = trace.notifications.pop
|
57
67
|
if curr.name == name
|
@@ -62,6 +72,10 @@ module Skylight
|
|
62
72
|
|
63
73
|
rescue Exception => e
|
64
74
|
error "Subscriber#finish error; msg=%s", e.message
|
75
|
+
debug "trace=%s", trace.inspect
|
76
|
+
debug "in: name=%s", name.inspect
|
77
|
+
debug "in: payload=%s", payload.inspect
|
78
|
+
t { e.backtrace.join("\n") }
|
65
79
|
nil
|
66
80
|
end
|
67
81
|
|
data/lib/skylight/version.rb
CHANGED
@@ -49,9 +49,12 @@ module Skylight
|
|
49
49
|
|
50
50
|
return true unless msg
|
51
51
|
|
52
|
-
|
52
|
+
case msg
|
53
|
+
when Messages::Trace
|
53
54
|
t { fmt "collector received trace" }
|
54
55
|
@batch.push(msg)
|
56
|
+
when Messages::Error
|
57
|
+
send_error(msg)
|
55
58
|
else
|
56
59
|
debug "Received unknown message; class=%s", msg.class.to_s
|
57
60
|
end
|
@@ -61,6 +64,22 @@ module Skylight
|
|
61
64
|
|
62
65
|
private
|
63
66
|
|
67
|
+
def send_error(msg)
|
68
|
+
res = @http_auth.post("/agent/error?hostname=#{escape(config[:'hostname'])}", reason: msg.reason, body: msg.body)
|
69
|
+
|
70
|
+
unless res.success?
|
71
|
+
if (400..499).include? @res.status
|
72
|
+
warn "error wasn't sent successfully; status=%s", res.status
|
73
|
+
end
|
74
|
+
|
75
|
+
warn "could not fetch report session token; status=%s", res.status
|
76
|
+
return
|
77
|
+
end
|
78
|
+
rescue Exception => e
|
79
|
+
error "exception; msg=%s; class=%s", e.message, e.class
|
80
|
+
t { e.backtrace.join("\n") }
|
81
|
+
end
|
82
|
+
|
64
83
|
def finish
|
65
84
|
t { fmt "collector finishing up" }
|
66
85
|
|
@@ -90,6 +109,11 @@ module Skylight
|
|
90
109
|
res = @http_auth.get("/agent/authenticate?hostname=#{escape(config[:'hostname'])}")
|
91
110
|
|
92
111
|
unless res.success?
|
112
|
+
if (400..499).include? @res.status
|
113
|
+
warn "token request rejected; status=%s", res.status
|
114
|
+
@http_report = nil
|
115
|
+
end
|
116
|
+
|
93
117
|
warn "could not fetch report session token; status=%s", res.status
|
94
118
|
return
|
95
119
|
end
|
@@ -98,12 +122,12 @@ module Skylight
|
|
98
122
|
tok = tok['token'] if tok
|
99
123
|
|
100
124
|
if tok
|
101
|
-
@refresh_at = now +
|
125
|
+
@refresh_at = now + 30
|
102
126
|
@http_report = Util::HTTP.new(config, :report)
|
103
127
|
@http_report.authentication = tok
|
104
128
|
else
|
105
129
|
if @http_report
|
106
|
-
@refresh_at = now +
|
130
|
+
@refresh_at = now + 30
|
107
131
|
end
|
108
132
|
warn "server did not return a session token"
|
109
133
|
end
|
@@ -21,7 +21,6 @@ module Skylight
|
|
21
21
|
:sockfile_path
|
22
22
|
|
23
23
|
def initialize(config, lockfile, srv, lockfile_path)
|
24
|
-
|
25
24
|
unless lockfile && srv
|
26
25
|
raise ArgumentError, "lockfile and unix domain server socket are required"
|
27
26
|
end
|
@@ -214,8 +213,8 @@ module Skylight
|
|
214
213
|
info "newer version of agent deployed - restarting; curr=%s; new=%s", VERSION, msg.version
|
215
214
|
reload(msg)
|
216
215
|
end
|
217
|
-
when Messages::
|
218
|
-
t { "received
|
216
|
+
when Messages::Base
|
217
|
+
t { "received message" }
|
219
218
|
@collector.submit(msg)
|
220
219
|
when :unknown
|
221
220
|
debug "received unknown message"
|
data/lib/sql_lexer.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require "strscan"
|
2
|
+
|
3
|
+
module SqlLexer
|
4
|
+
class Lexer
|
5
|
+
# SQL identifiers and key words must begin with a letter (a-z, but also
|
6
|
+
# letters with diacritical marks and non-Latin letters) or an underscore
|
7
|
+
# (_). Subsequent characters in an identifier or key word can be letters,
|
8
|
+
# underscores, digits (0-9), or dollar signs ($). Note that dollar signs
|
9
|
+
# are not allowed in identifiers according to the letter of the SQL
|
10
|
+
# standard, so their use might render applications less portable. The SQL
|
11
|
+
# standard will not define a key word that contains digits or starts or
|
12
|
+
# ends with an underscore, so identifiers of this form are safe against
|
13
|
+
# possible conflict with future extensions of the standard.
|
14
|
+
StartID = %q<\p{Alpha}_>
|
15
|
+
PartID = %q<\p{Alnum}_$>
|
16
|
+
OpPart = %q<\+|\-(?!-)|\*|/(?!\*)|\<|\>|=|~|!|@|#|%|\^|&|\||\?|\.|,|\(|\)>
|
17
|
+
WS = %q< \t\r\n>
|
18
|
+
OptWS = %Q<[#{WS}]*>
|
19
|
+
|
20
|
+
InOp = %q<IN>
|
21
|
+
SpecialOps = %Q<#{InOp}>
|
22
|
+
|
23
|
+
StartQuotedID = %Q<">
|
24
|
+
StartTickedID = %Q<`>
|
25
|
+
StartString = %Q<'>
|
26
|
+
StartDigit = %q<[\p{Digit}\.]>
|
27
|
+
StartBind = %Q<#{StartString}|#{StartDigit}|#{SpecialOps}>
|
28
|
+
StartNonBind = %Q<#{StartQuotedID}|#{StartTickedID}|\\$(?=\\p{Digit})>
|
29
|
+
TableNext = %Q<(#{OptWS}((?=#{StartQuotedID})|(?=#{StartTickedID}))|[#{WS}]+(?=[#{StartID}]))>
|
30
|
+
StartAnyId = %Q<"#{StartID}>
|
31
|
+
Placeholder = %q<\$\p{Digit}+>
|
32
|
+
|
33
|
+
AfterID = %Q<[#{WS};#{StartNonBind}]|(?:#{OpPart})|$>
|
34
|
+
ID = %Q<[#{StartID}][#{PartID}]*(?=#{AfterID})>
|
35
|
+
AfterOp = %Q<[#{WS}]|[#{StartAnyId}]|[#{StartBind}]|(#{StartNonBind})|$>
|
36
|
+
Op = %Q<(?:#{OpPart})+(?=#{AfterOp})>
|
37
|
+
QuotedID = %Q<#{StartQuotedID}(?:[^"]|"")*">
|
38
|
+
TickedID = %Q<#{StartTickedID}(?:[^`]|``)*`>
|
39
|
+
NonBind = %Q<#{ID}|#{Op}|#{QuotedID}|#{TickedID}|#{Placeholder}>
|
40
|
+
QuotedTable = %Q<#{TickedID}|#{QuotedID}>
|
41
|
+
|
42
|
+
String = %Q<#{StartString}(?:[^']|'')*'>
|
43
|
+
|
44
|
+
Digits = %q<\p{Digit}+>
|
45
|
+
OptDigits = %q<\p{Digit}*>
|
46
|
+
Exponent = %Q<e[+\-]?#{Digits}>
|
47
|
+
OptExponent = %Q<(?:#{Exponent})?>
|
48
|
+
HeadDecimal = %Q<#{Digits}\\.#{OptDigits}#{OptExponent}>
|
49
|
+
TailDecimal = %Q<#{OptDigits}\\.#{Digits}#{OptExponent}>
|
50
|
+
ExpDecimal = %Q<#{Digits}#{Exponent}>
|
51
|
+
|
52
|
+
Number = %Q<#{HeadDecimal}|#{TailDecimal}|#{ExpDecimal}|#{Digits}>
|
53
|
+
|
54
|
+
TkWS = %r<[#{WS}]+>
|
55
|
+
TkOptWS = %r<[#{WS}]*>
|
56
|
+
TkOp = %r<[#{OpPart}]>
|
57
|
+
TkPlaceholder = %r<#{Placeholder}>
|
58
|
+
TkNonBind = %r<#{NonBind}>
|
59
|
+
TkQuotedTable = %r<#{QuotedTable}>i
|
60
|
+
TkUpdateTable = %r<UPDATE#{TableNext}>i
|
61
|
+
TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>i
|
62
|
+
TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}>i
|
63
|
+
TkFromTable = %r<FROM#{TableNext}>i
|
64
|
+
TkID = %r<#{ID}>
|
65
|
+
TkEnd = %r<;?[#{WS}]*>
|
66
|
+
TkBind = %r<#{String}|#{Number}>
|
67
|
+
TkIn = %r<#{InOp}>i
|
68
|
+
TkSpecialOp = %r<#{SpecialOps}>i
|
69
|
+
|
70
|
+
STATE_HANDLERS = {
|
71
|
+
begin: :process_begin,
|
72
|
+
first_token: :process_first_token,
|
73
|
+
tokens: :process_tokens,
|
74
|
+
bind: :process_bind,
|
75
|
+
non_bind: :process_non_bind,
|
76
|
+
table_name: :process_table_name,
|
77
|
+
end: :process_end,
|
78
|
+
special: :process_special,
|
79
|
+
in: :process_in
|
80
|
+
}
|
81
|
+
|
82
|
+
def self.bindify(string)
|
83
|
+
new(string).tap do |scanner|
|
84
|
+
scanner.process
|
85
|
+
return scanner.title, scanner.output, scanner.binds
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :output, :binds, :title
|
90
|
+
|
91
|
+
def initialize(string)
|
92
|
+
@scanner = StringScanner.new(string)
|
93
|
+
@state = :begin
|
94
|
+
@input = string
|
95
|
+
@output = string.dup
|
96
|
+
@binds = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def process
|
100
|
+
@operation = @table = @title = nil
|
101
|
+
|
102
|
+
while @state
|
103
|
+
if ENV["DEBUG"]
|
104
|
+
p @state
|
105
|
+
p @scanner
|
106
|
+
end
|
107
|
+
|
108
|
+
send STATE_HANDLERS[@state]
|
109
|
+
end
|
110
|
+
|
111
|
+
pos = 0
|
112
|
+
removed = 0
|
113
|
+
extracted_binds = Array.new(@binds.size / 2)
|
114
|
+
|
115
|
+
if @operation && @table
|
116
|
+
table = @input[@table[0], @table[1] - @table[0]]
|
117
|
+
@title = "#{@operation} #{table}"
|
118
|
+
end
|
119
|
+
|
120
|
+
while pos < @binds.size
|
121
|
+
slice = @output.slice!(@binds[pos] - removed, @binds[pos+1])
|
122
|
+
@output.insert(@binds[pos] - removed, '?')
|
123
|
+
extracted_binds[pos/2] = slice
|
124
|
+
removed += slice.size - 1
|
125
|
+
pos += 2
|
126
|
+
end
|
127
|
+
|
128
|
+
@binds = extracted_binds
|
129
|
+
end
|
130
|
+
|
131
|
+
def process_begin
|
132
|
+
@scanner.scan(TkOptWS)
|
133
|
+
@state = :first_token
|
134
|
+
end
|
135
|
+
|
136
|
+
def process_first_token
|
137
|
+
if @scanner.skip(/SELECT\s+/i)
|
138
|
+
@operation = :"SELECT FROM"
|
139
|
+
@state = :tokens
|
140
|
+
return
|
141
|
+
end
|
142
|
+
|
143
|
+
if @scanner.skip(TkUpdateTable)
|
144
|
+
@operation = :UPDATE
|
145
|
+
elsif @scanner.skip(TkInsertTable)
|
146
|
+
@operation = :"INSERT INTO"
|
147
|
+
elsif @scanner.skip(TkDeleteTable)
|
148
|
+
@operation = :"DELETE FROM"
|
149
|
+
end
|
150
|
+
|
151
|
+
@state = :table_name
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_table_name
|
155
|
+
pos = @scanner.pos
|
156
|
+
|
157
|
+
if @scanner.skip(TkQuotedTable)
|
158
|
+
@table = [pos + 1, @scanner.pos - 1]
|
159
|
+
elsif @scanner.skip(TkID)
|
160
|
+
@table = [pos, @scanner.pos]
|
161
|
+
end
|
162
|
+
|
163
|
+
@state = :tokens
|
164
|
+
end
|
165
|
+
|
166
|
+
def process_tokens
|
167
|
+
@scanner.skip(TkOptWS)
|
168
|
+
|
169
|
+
if @operation == :"SELECT FROM" && !@table && @scanner.skip(TkFromTable)
|
170
|
+
@state = :table_name
|
171
|
+
elsif @scanner.match?(TkSpecialOp)
|
172
|
+
@state = :special
|
173
|
+
elsif @scanner.match?(TkBind)
|
174
|
+
@state = :bind
|
175
|
+
elsif @scanner.match?(TkNonBind)
|
176
|
+
@state = :non_bind
|
177
|
+
else
|
178
|
+
@state = :end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def process_special
|
183
|
+
if @scanner.skip(TkIn)
|
184
|
+
@scanner.skip(TkOptWS)
|
185
|
+
@scanner.skip(/\(/)
|
186
|
+
@state = :in
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def process_in
|
191
|
+
nest = 1
|
192
|
+
iterations = 0
|
193
|
+
|
194
|
+
@skip_binds = true
|
195
|
+
pos = @scanner.pos - 1
|
196
|
+
|
197
|
+
while nest > 0
|
198
|
+
iterations += 1
|
199
|
+
|
200
|
+
if iterations > 10_000
|
201
|
+
raise "The SQL '#{@scanner.string}' could not be parsed because of too many iterations in IN"
|
202
|
+
end
|
203
|
+
|
204
|
+
if ENV["DEBUG"]
|
205
|
+
p @state
|
206
|
+
p @scanner
|
207
|
+
p nest
|
208
|
+
end
|
209
|
+
|
210
|
+
if @scanner.skip(/\(/)
|
211
|
+
nest += 1
|
212
|
+
process_tokens
|
213
|
+
elsif @scanner.skip(/\)/)
|
214
|
+
nest -= 1
|
215
|
+
break if nest.zero?
|
216
|
+
process_tokens
|
217
|
+
else
|
218
|
+
process_tokens
|
219
|
+
end
|
220
|
+
|
221
|
+
send STATE_HANDLERS[@state]
|
222
|
+
end
|
223
|
+
|
224
|
+
@binds << pos
|
225
|
+
@binds << @scanner.pos - pos
|
226
|
+
|
227
|
+
@skip_binds = false
|
228
|
+
|
229
|
+
@state = :tokens
|
230
|
+
end
|
231
|
+
|
232
|
+
def process_non_bind
|
233
|
+
@scanner.skip(TkNonBind)
|
234
|
+
@state = :tokens
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_bind
|
238
|
+
pos = @scanner.pos
|
239
|
+
size = @scanner.skip(TkBind)
|
240
|
+
|
241
|
+
unless @skip_binds
|
242
|
+
@binds << pos
|
243
|
+
@binds << size
|
244
|
+
end
|
245
|
+
|
246
|
+
@state = :tokens
|
247
|
+
end
|
248
|
+
|
249
|
+
def process_end
|
250
|
+
@scanner.skip(TkEnd)
|
251
|
+
|
252
|
+
unless @scanner.eos?
|
253
|
+
raise "The SQL '#{@scanner.string}' could not be parsed"
|
254
|
+
end
|
255
|
+
|
256
|
+
@state = nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skylight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.0.beta.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tilde, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/skylight/messages/base.rb
|
47
47
|
- lib/skylight/messages/batch.rb
|
48
48
|
- lib/skylight/messages/endpoint.rb
|
49
|
+
- lib/skylight/messages/error.rb
|
49
50
|
- lib/skylight/messages/event.rb
|
50
51
|
- lib/skylight/messages/hello.rb
|
51
52
|
- lib/skylight/messages/span.rb
|
@@ -127,6 +128,9 @@ files:
|
|
127
128
|
- lib/skylight/worker/embedded.rb
|
128
129
|
- lib/skylight/worker/server.rb
|
129
130
|
- lib/skylight/worker/standalone.rb
|
131
|
+
- lib/sql_lexer.rb
|
132
|
+
- lib/sql_lexer/lexer.rb
|
133
|
+
- lib/sql_lexer/version.rb
|
130
134
|
- CHANGELOG.md
|
131
135
|
- README.md
|
132
136
|
- bin/skylight
|
@@ -144,9 +148,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
148
|
version: 1.9.2
|
145
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
150
|
requirements:
|
147
|
-
- - '
|
151
|
+
- - '>'
|
148
152
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
153
|
+
version: 1.3.1
|
150
154
|
requirements: []
|
151
155
|
rubyforge_project:
|
152
156
|
rubygems_version: 2.0.3
|