skylight 0.1.8 → 0.2.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|