anbt-sql-formatter 0.0.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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/anbt-sql-formatter.gemspec +24 -0
- data/bin/anbt-sql-formatter +50 -0
- data/lgpl-2.1.txt +504 -0
- data/lib/anbt-sql-formatter/coarse-tokenizer.rb +174 -0
- data/lib/anbt-sql-formatter/constants.rb +81 -0
- data/lib/anbt-sql-formatter/exception.rb +30 -0
- data/lib/anbt-sql-formatter/formatter.rb +409 -0
- data/lib/anbt-sql-formatter/helper.rb +73 -0
- data/lib/anbt-sql-formatter/parser.rb +327 -0
- data/lib/anbt-sql-formatter/rule.rb +121 -0
- data/lib/anbt-sql-formatter/token.rb +79 -0
- data/lib/anbt-sql-formatter/version.rb +7 -0
- data/misc/anbt-sql-formatter-customize-example +65 -0
- data/misc/anbt-sql-formatter-for-sakura-editor.js +165 -0
- data/readme.ja.txt +107 -0
- data/readme.txt +58 -0
- data/sample.sql +120 -0
- data/setup.rb +1585 -0
- data/test/helper.rb +17 -0
- data/test/test_coarse-tokenizer.rb +360 -0
- data/test/test_formatter.rb +489 -0
- data/test/test_helper.rb +23 -0
- data/test/test_parser.rb +370 -0
- data/test/test_rule.rb +30 -0
- data/uninstall.rb +20 -0
- metadata +84 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
=begin
|
6
|
+
エスケープ文字
|
7
|
+
=end
|
8
|
+
|
9
|
+
class CoarseToken
|
10
|
+
attr_accessor :_type, :string
|
11
|
+
|
12
|
+
def initialize(type, str)
|
13
|
+
@_type = type
|
14
|
+
@string = str
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
%Q!<#{@_type}>%s</>! % [@string.gsub("\n", "<br>")]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
class CoarseTokenizer
|
24
|
+
def initialize
|
25
|
+
@comment_single_start = /--/
|
26
|
+
@comment_multi_start = /\/\*/
|
27
|
+
@comment_multi_end = /\*\//
|
28
|
+
end
|
29
|
+
|
30
|
+
=begin rdoc
|
31
|
+
These are exclusive:
|
32
|
+
* double quote string
|
33
|
+
* single quote string
|
34
|
+
* single line comment
|
35
|
+
* multiple line comment
|
36
|
+
|
37
|
+
ソース先頭から見ていって先に現れたものが優先される。
|
38
|
+
|
39
|
+
@result <= @buf <= @str
|
40
|
+
=end
|
41
|
+
|
42
|
+
def tokenize(str)
|
43
|
+
@str = str
|
44
|
+
@str.gsub!("\r\n", "\n")
|
45
|
+
out_of_quote_single = true
|
46
|
+
out_of_quote_double = true
|
47
|
+
out_of_comment_single = true
|
48
|
+
out_of_comment_multi = true
|
49
|
+
|
50
|
+
@result = []
|
51
|
+
@buf = ""
|
52
|
+
@mode = :plain
|
53
|
+
|
54
|
+
while @str.size > 0
|
55
|
+
|
56
|
+
if /\A(")/ =~ str && out_of_quote_double &&
|
57
|
+
out_of_quote_single && out_of_comment_single && out_of_comment_multi
|
58
|
+
## begin double quote
|
59
|
+
|
60
|
+
length = $1.size
|
61
|
+
shift_token(length, :plain, :quote_double, :start)
|
62
|
+
out_of_quote_double = false
|
63
|
+
|
64
|
+
elsif /\A(")/ =~ str && !(out_of_quote_double) &&
|
65
|
+
out_of_quote_single && out_of_comment_single && out_of_comment_multi
|
66
|
+
## end double quote
|
67
|
+
|
68
|
+
length = $1.size
|
69
|
+
if /\A("")/ =~ str ## escaped double quote
|
70
|
+
shift_to_buf(2)
|
71
|
+
else
|
72
|
+
shift_token(length, :quote_double, :plain, :end)
|
73
|
+
out_of_quote_double = true
|
74
|
+
end
|
75
|
+
|
76
|
+
elsif /\A(')/ =~ str && out_of_quote_single &&
|
77
|
+
out_of_quote_double && out_of_comment_single && out_of_comment_multi
|
78
|
+
## begin single quote
|
79
|
+
|
80
|
+
length = $1.size
|
81
|
+
shift_token(length, :plain, :quote_single, :start)
|
82
|
+
out_of_quote_single = false
|
83
|
+
elsif /\A(')/ =~ str && !(out_of_quote_single) &&
|
84
|
+
out_of_quote_double && out_of_comment_single && out_of_comment_multi
|
85
|
+
## end single quote
|
86
|
+
|
87
|
+
length = $1.size
|
88
|
+
if /\A('')/ =~ @str ## escaped single quote
|
89
|
+
shift_to_buf(2)
|
90
|
+
else
|
91
|
+
shift_token(length, :quote_single, :plain, :end)
|
92
|
+
out_of_quote_single = true
|
93
|
+
end
|
94
|
+
|
95
|
+
elsif /\A(#{@comment_single_start})/ =~ str && out_of_comment_single &&
|
96
|
+
out_of_quote_single && out_of_quote_double && out_of_comment_multi
|
97
|
+
## begin single line comment
|
98
|
+
|
99
|
+
length = $1.size
|
100
|
+
shift_token(length, :plain, :comment_single, :start)
|
101
|
+
out_of_comment_single = false
|
102
|
+
|
103
|
+
elsif /\A(\n)/ =~ str && !(out_of_comment_single) &&
|
104
|
+
out_of_quote_single && out_of_quote_double && out_of_comment_multi
|
105
|
+
## end single line comment
|
106
|
+
|
107
|
+
length = $1.size
|
108
|
+
shift_token(length, :comment_single, :plain, :end)
|
109
|
+
out_of_comment_single = true
|
110
|
+
|
111
|
+
elsif /\A(#{@comment_multi_start})/ =~ str &&
|
112
|
+
out_of_quote_single && out_of_quote_double && out_of_comment_single && out_of_comment_multi
|
113
|
+
## begin multi line comment
|
114
|
+
|
115
|
+
length = $1.size
|
116
|
+
shift_token(length, :plain, :comment_multi, :start)
|
117
|
+
out_of_comment_multi = false
|
118
|
+
|
119
|
+
elsif /\A(#{@comment_multi_end})/ =~ str &&
|
120
|
+
out_of_quote_single && out_of_quote_double && out_of_comment_single && !(out_of_comment_multi)
|
121
|
+
## end multi line comment
|
122
|
+
|
123
|
+
length = $1.size
|
124
|
+
shift_token(length, :comment_multi, :plain, :end)
|
125
|
+
out_of_comment_multi = true
|
126
|
+
|
127
|
+
elsif /\A\\/ =~ str
|
128
|
+
## escape char
|
129
|
+
shift_to_buf(2)
|
130
|
+
|
131
|
+
else
|
132
|
+
shift_to_buf(1)
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
@result << CoarseToken.new(@mode, @buf+@str) if (@buf+@str).size > 0
|
137
|
+
|
138
|
+
@result
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def shift_to_buf(n)
|
143
|
+
@buf << @str[0...n]
|
144
|
+
@str[0...n] = ""
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
def shift_token(length, type, mode, flag)
|
149
|
+
case flag
|
150
|
+
when :start
|
151
|
+
@result << CoarseToken.new(type, @buf) if @buf.size > 0
|
152
|
+
@buf = @str[0..(length-1)] # <length> char from head
|
153
|
+
when :end
|
154
|
+
@result << CoarseToken.new(type, @buf+@str[0..(length-1)]) if @buf.size > 0
|
155
|
+
@buf = ""
|
156
|
+
else
|
157
|
+
raise "must not happen"
|
158
|
+
end
|
159
|
+
|
160
|
+
@str[0..(length-1)] = ""
|
161
|
+
@mode = mode
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
if $0 == __FILE__
|
167
|
+
tok = CoarseTokenizer.new
|
168
|
+
src = File.read(ARGV[0])
|
169
|
+
coarse_tokens = tok.tokenize(src)
|
170
|
+
|
171
|
+
coarse_tokens.each{|t|
|
172
|
+
puts t.to_s
|
173
|
+
}
|
174
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
AnbtSqlFormatter: SQL整形ツール. SQL文を決められたルールに従い整形します。
|
5
|
+
|
6
|
+
フォーマットを実施するためには、入力されるSQLがSQL文として妥当であることが前提条件となります。
|
7
|
+
|
8
|
+
このクラスが準拠するSQL整形のルールについては、下記URLを参照ください。
|
9
|
+
http://homepage2.nifty.com/igat/igapyon/diary/2005/ig050613.html
|
10
|
+
|
11
|
+
このクラスには ANSI SQLの予約語一覧が蓄えられます。
|
12
|
+
|
13
|
+
@author IGA Tosiki
|
14
|
+
@author sonota
|
15
|
+
=end
|
16
|
+
|
17
|
+
|
18
|
+
class AnbtSql
|
19
|
+
class Constants
|
20
|
+
# ANSI SQL キーワード
|
21
|
+
SQL_RESERVED_WORDS =
|
22
|
+
[
|
23
|
+
# ANSI SQL89
|
24
|
+
"ALL", "AND", "ANY", "AS", "ASC", "AUTHORIZATION", "AVG", "BEGIN",
|
25
|
+
"BETWEEN", "BY", "CHAR", "CHARACTER", "CHECK", "CLOSE", "COBOL",
|
26
|
+
"COMMIT", "CONTINUE", "COUNT", "CREATE", "CURRENT", "CURSOR",
|
27
|
+
"DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DESC",
|
28
|
+
"DISTINCT", "DOUBLE", "END", "ESCAPE", "EXEC", "EXISTS", "FETCH",
|
29
|
+
"FLOAT", "FOR", "FOREIGN", "FORTRAN", "FOUND", "FROM", "GO",
|
30
|
+
"GOTO", "GRANT", "GROUP", "HAVING", "IN", "INDICATOR", "INSERT",
|
31
|
+
"INT", "INTEGER", "INTO", "IS", "KEY", "LANGUAGE", "LIKE", "MAX",
|
32
|
+
"MIN", "MODULE", "NOT", "NULL", "NUMERIC", "OF", "ON", "OPEN",
|
33
|
+
"OPTION", "OR", "ORDER", "PASCAL", "PLI", "PRECISION", "PRIMARY",
|
34
|
+
"PRIVILEGES", "PROCEDURE", "PUBLIC", "REAL", "REFERENCES",
|
35
|
+
"ROLLBACK", "SCHEMA", "SECTION", "SELECT", "SET", "SMALLINT",
|
36
|
+
"SOME", "SQL", "SQLCODE", "SQLERROR", "SUM", "TABLE", "TO",
|
37
|
+
"UNION", "UNIQUE", "UPDATE", "USER", "VALUES", "VIEW",
|
38
|
+
"WHENEVER", "WHERE", "WITH", "WORK",
|
39
|
+
# ANSI SQL92
|
40
|
+
"ABSOLUTE", "ACTION", "ADD", "ALLOCATE", "ALTER", "ARE",
|
41
|
+
"ASSERTION", "AT", "BIT", "BIT_LENGTH", "BOTH", "CASCADE",
|
42
|
+
"CASCADED", "CASE", "CAST", "CATALOG", "CHAR_LENGTH",
|
43
|
+
"CHARACTER_LENGTH", "COALESCE", "COLLATE", "COLLATION", "COLUMN",
|
44
|
+
"CONNECT", "CONNECTION", "CONSTRAINT", "CONSTRAINTS", "CONVERT",
|
45
|
+
"CORRESPONDING", "CROSS", "CURRENT_DATE", "CURRENT_TIME",
|
46
|
+
"CURRENT_TIMESTAMP", "CURRENT_USER", "DATE", "DAY", "DEALLOATE",
|
47
|
+
"DEFERRABLE", "DEFERRED", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS",
|
48
|
+
"DISCONNECT", "DOMAIN", "DROP", "ELSE", "END-EXEC", "EXCEPT",
|
49
|
+
"EXCEPTION", "EXECUTE", "EXTERNAL", "EXTRACT", "FALSE", "FIRST",
|
50
|
+
"FULL", "GET", "GLOBAL", "HOUR", "IDENTITY", "IMMEDIATE",
|
51
|
+
"INITIALLY", "INNER", "INPUT", "INSENSITIVE", "INTERSECT",
|
52
|
+
"INTERVAL", "ISOLATION", "JOIN", "LAST", "LEADING", "LEFT",
|
53
|
+
"LEVEL", "LOCAL", "LOWER", "MATCH", "MINUTE", "MONTH", "NAMES",
|
54
|
+
"NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NULLIF",
|
55
|
+
"OCTET_LENGTH", "ONLY", "OUTER", "OUTPUT", "OVERLAPS", "PAD",
|
56
|
+
"PARTIAL", "POSITION", "PREPARE", "PRESERVE", "PRIOR", "READ",
|
57
|
+
"RELATIVE", "RESTRICT", "REVOKE", "RIGHT", "ROWS", "SCROLL",
|
58
|
+
"SECOND", "SESSION", "SESSION_USER", "SIZE", "SPACE", "SQLSTATE",
|
59
|
+
"SUBSTRING", "SYSTEM_USER", "TEMPORARY", "THEN", "TIME",
|
60
|
+
"TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TRAILING",
|
61
|
+
"TRANSACTION", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE",
|
62
|
+
"UNKNOWN", "UPPER", "USAGE", "USING", "VALUE", "VARCHAR",
|
63
|
+
"VARYING", "WHEN", "WRITE", "YEAR", "ZONE",
|
64
|
+
# ANSI SQL99
|
65
|
+
"ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ARRAY", "BEFORE",
|
66
|
+
"BINARY", "BLOB", "BOOLEAN", "BREADTH", "CALL", "CLASS", "CLOB",
|
67
|
+
"COMPLETION", "CONDITION", "CONSTRUCTOR", "CUBE", "CURRENT_PATH",
|
68
|
+
"CURRENT_ROLE", "CYCLE", "DATA", "DEPTH", "DEREF", "DESTROY",
|
69
|
+
"DESTRUCTOR", "DETERMINISTIC", "DICTIONARY", "DO", "DYNAMIC",
|
70
|
+
"EACH", "ELSEIF", "EQUALS", "EVERY", "EXIT", "FREE", "FUNCTION",
|
71
|
+
"GENERAL", "GROUPING", "HANDLER", "HOST", "IF", "IGNORE",
|
72
|
+
"INITIALIZE", "INOUT", "ITERATE", "LARGE", "LATERAL", "LEAVE",
|
73
|
+
"LESS", "LIMIT", "LIST", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR",
|
74
|
+
"LONG", "LOOP", "MAP", "MODIFIES", "MODIFY", "NCLOB", "NEW",
|
75
|
+
"NONE", "NUMBER", "OBJECT", "OFF", "OLD", "OPERATION",
|
76
|
+
"ORDINALITY", "OUT", "PARAMETER", "PARAMETERS", "PATH", "POSTFIX",
|
77
|
+
"PREFIX", "PREORDER", "RAW", "READS", "RECURSIVE", "REDO",
|
78
|
+
# ANSI SQLではないのだが とても良く使われる構文
|
79
|
+
"TRUNCATE" ]
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
BlancoSqlFormatterException : SQL整形ツールの例外を表します。
|
5
|
+
|
6
|
+
@author IGA Tosiki : 新規作成 at 2005.08.03
|
7
|
+
=end
|
8
|
+
|
9
|
+
=begin rdoc
|
10
|
+
* Rubyの流儀に合わせて "xxxError" とした方が良いかもしれない。
|
11
|
+
|
12
|
+
@author sonota (2009-11-xx)
|
13
|
+
=end
|
14
|
+
|
15
|
+
require "pp"
|
16
|
+
|
17
|
+
|
18
|
+
class AnbtSql
|
19
|
+
class FormatterException < IOError
|
20
|
+
def initialize(msg=nil)
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class IndexOutOfBoundsException < StandardError
|
27
|
+
def initialize(msg=nil)
|
28
|
+
super(msg)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,409 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
require "anbt-sql-formatter/rule"
|
6
|
+
require "anbt-sql-formatter/parser"
|
7
|
+
require "anbt-sql-formatter/exception"
|
8
|
+
require "anbt-sql-formatter/helper" # Stack
|
9
|
+
|
10
|
+
|
11
|
+
class AnbtSql
|
12
|
+
class Formatter
|
13
|
+
|
14
|
+
@rule = nil
|
15
|
+
|
16
|
+
def initialize(rule)
|
17
|
+
@rule = rule
|
18
|
+
@parser = AnbtSql::Parser.new(@rule)
|
19
|
+
|
20
|
+
# 丸カッコが関数のものかどうかを記憶
|
21
|
+
@function_bracket = Stack.new
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def split_by_semicolon(tokens)
|
26
|
+
statements = []
|
27
|
+
buf = []
|
28
|
+
tokens.each{|token|
|
29
|
+
if token.string == ";"
|
30
|
+
statements << buf
|
31
|
+
buf = []
|
32
|
+
else
|
33
|
+
buf << token
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
statements << buf
|
38
|
+
|
39
|
+
statements
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
##
|
44
|
+
# 与えられたSQLを整形した文字列を返します。
|
45
|
+
#
|
46
|
+
# 改行で終了するSQL文は、整形後も改行付きであるようにします。
|
47
|
+
# sql_str:: 整形前のSQL文
|
48
|
+
def format(sql_str)
|
49
|
+
@function_bracket.clear()
|
50
|
+
begin
|
51
|
+
isSqlEndsWithNewLine = false
|
52
|
+
if sql_str.endsWith("\n")
|
53
|
+
isSqlEndsWithNewLine = true
|
54
|
+
end
|
55
|
+
|
56
|
+
tokens = @parser.parse(sql_str)
|
57
|
+
|
58
|
+
statements = split_by_semicolon(tokens)
|
59
|
+
|
60
|
+
statements = statements.map{|tokens|
|
61
|
+
format_list(tokens)
|
62
|
+
}
|
63
|
+
|
64
|
+
# 変換結果を文字列に戻す。
|
65
|
+
after = statements.map{|tokens|
|
66
|
+
tokens.map{ |t| t.string }.join("")
|
67
|
+
}.join("\n;\n\n").sub( /\n\n\Z/, "" )
|
68
|
+
|
69
|
+
after += "\n" if isSqlEndsWithNewLine
|
70
|
+
|
71
|
+
return after
|
72
|
+
rescue => e
|
73
|
+
raise AnbtSql::FormatterException.new, e.message, e.backtrace
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def modify_keyword_case(tokens)
|
79
|
+
# SQLキーワードは大文字とする。or ...
|
80
|
+
tokens.each{ |token|
|
81
|
+
next if token._type != AnbtSql::TokenConstants::KEYWORD
|
82
|
+
|
83
|
+
case @rule.keyword
|
84
|
+
when AnbtSql::Rule::KEYWORD_NONE
|
85
|
+
;
|
86
|
+
when AnbtSql::Rule::KEYWORD_UPPER_CASE
|
87
|
+
token.string.upcase!
|
88
|
+
when AnbtSql::Rule::KEYWORD_LOWER_CASE
|
89
|
+
token.string.downcase!
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
##
|
96
|
+
# .
|
97
|
+
# ["(", "+", ")"] => ["(+)"]
|
98
|
+
def concat_operator_for_oracle(tokens)
|
99
|
+
index = 0
|
100
|
+
# Length of tokens changes in loop!
|
101
|
+
while index < tokens.size - 2
|
102
|
+
if (tokens[index ].string == "(" &&
|
103
|
+
tokens[index + 1].string == "+" &&
|
104
|
+
tokens[index + 2].string == ")")
|
105
|
+
tokens[index].string = "(+)"
|
106
|
+
tokens.remove(index + 1)
|
107
|
+
tokens.remove(index + 1)
|
108
|
+
end
|
109
|
+
index += 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def remove_symbol_side_space(tokens)
|
115
|
+
prevToken = nil
|
116
|
+
|
117
|
+
(tokens.size - 1).downto(1){|index|
|
118
|
+
token = tokens.get(index)
|
119
|
+
prevToken = tokens.get(index - 1)
|
120
|
+
|
121
|
+
if (token._type == AnbtSql::TokenConstants::SPACE &&
|
122
|
+
(prevToken._type == AnbtSql::TokenConstants::SYMBOL ||
|
123
|
+
prevToken._type == AnbtSql::TokenConstants::COMMENT))
|
124
|
+
tokens.remove(index)
|
125
|
+
elsif ((token._type == AnbtSql::TokenConstants::SYMBOL ||
|
126
|
+
token._type == AnbtSql::TokenConstants::COMMENT) &&
|
127
|
+
prevToken._type == AnbtSql::TokenConstants::SPACE)
|
128
|
+
tokens.remove(index - 1)
|
129
|
+
elsif (token._type == AnbtSql::TokenConstants::SPACE)
|
130
|
+
token.string = " "
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def insert_space_between_tokens(tokens)
|
137
|
+
index = 1
|
138
|
+
|
139
|
+
# Length of tokens changes in loop!
|
140
|
+
while index < tokens.size
|
141
|
+
prev = tokens.get(index - 1)
|
142
|
+
token = tokens.get(index )
|
143
|
+
|
144
|
+
if (prev._type != AnbtSql::TokenConstants::SPACE &&
|
145
|
+
token._type != AnbtSql::TokenConstants::SPACE)
|
146
|
+
# カンマの後にはスペース入れない
|
147
|
+
if not @rule.space_after_comma
|
148
|
+
if prev.string == ","
|
149
|
+
index += 1 ; next
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# 関数名の後ろにはスペースは入れない
|
154
|
+
# no space after function name
|
155
|
+
if (@rule.function?(prev.string) &&
|
156
|
+
token.string.equals("("))
|
157
|
+
index += 1 ; next
|
158
|
+
end
|
159
|
+
|
160
|
+
tokens.add(index,
|
161
|
+
AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE, " ")
|
162
|
+
)
|
163
|
+
end
|
164
|
+
index += 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def format_list_main_loop(tokens)
|
170
|
+
# インデントを整える。
|
171
|
+
indent = 0
|
172
|
+
# 丸カッコのインデント位置を覚える。
|
173
|
+
bracket_indent = Stack.new
|
174
|
+
|
175
|
+
prev = AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE,
|
176
|
+
" ")
|
177
|
+
|
178
|
+
index = 0
|
179
|
+
# Length of tokens changes in loop!
|
180
|
+
while index < tokens.size
|
181
|
+
token = tokens.get(index)
|
182
|
+
|
183
|
+
if token._type == AnbtSql::TokenConstants::SYMBOL # ****
|
184
|
+
|
185
|
+
# indentを1つ増やし、'('のあとで改行。
|
186
|
+
if token.string == "("
|
187
|
+
@function_bracket.push( @rule.function?(prev.string) ? true : false )
|
188
|
+
bracket_indent.push(indent)
|
189
|
+
indent += 1
|
190
|
+
index += insert_return_and_indent(tokens, index + 1, indent)
|
191
|
+
|
192
|
+
# indentを1つ増やし、')'の前と後ろで改行。
|
193
|
+
elsif token.string == ")"
|
194
|
+
indent = (bracket_indent.pop()).to_i
|
195
|
+
index += insert_return_and_indent(tokens, index, indent)
|
196
|
+
@function_bracket.pop()
|
197
|
+
|
198
|
+
# ','の前で改行
|
199
|
+
elsif token.string == ","
|
200
|
+
index += insert_return_and_indent(tokens, index, indent, "x")
|
201
|
+
|
202
|
+
elsif token.string == ";"
|
203
|
+
# 2005.07.26 Tosiki Iga とりあえずセミコロンでSQL文がつぶれないように改良
|
204
|
+
indent = 0
|
205
|
+
index += insert_return_and_indent(tokens, index, indent)
|
206
|
+
end
|
207
|
+
|
208
|
+
elsif token._type == AnbtSql::TokenConstants::KEYWORD # ****
|
209
|
+
|
210
|
+
# indentを2つ増やし、キーワードの後ろで改行
|
211
|
+
if (token.string.equalsIgnoreCase("DELETE") ||
|
212
|
+
token.string.equalsIgnoreCase("SELECT") ||
|
213
|
+
token.string.equalsIgnoreCase("UPDATE") )
|
214
|
+
indent += 2
|
215
|
+
index += insert_return_and_indent(tokens, index + 1, indent, "+2")
|
216
|
+
end
|
217
|
+
|
218
|
+
# indentを1つ増やし、キーワードの後ろで改行
|
219
|
+
if @rule.kw_plus1_indent_x_nl.any?{ |kw| token.string.equalsIgnoreCase(kw) }
|
220
|
+
indent += 1
|
221
|
+
index += insert_return_and_indent(tokens, index + 1, indent)
|
222
|
+
end
|
223
|
+
|
224
|
+
# キーワードの前でindentを1つ減らして改行、キーワードの後ろでindentを戻して改行。
|
225
|
+
if @rule.kw_minus1_indent_nl_x_plus1_indent.any?{ |kw| token.string.equalsIgnoreCase(kw) }
|
226
|
+
index += insert_return_and_indent(tokens, index , indent - 1)
|
227
|
+
index += insert_return_and_indent(tokens, index + 1, indent )
|
228
|
+
end
|
229
|
+
|
230
|
+
# キーワードの前でindentを1つ減らして改行、キーワードの後ろでindentを戻して改行。
|
231
|
+
if (token.string.equalsIgnoreCase("VALUES"))
|
232
|
+
indent -= 1
|
233
|
+
index += insert_return_and_indent(tokens, index, indent)
|
234
|
+
end
|
235
|
+
|
236
|
+
# キーワードの前でindentを1つ減らして改行
|
237
|
+
if (token.string.equalsIgnoreCase("END"))
|
238
|
+
indent -= 1
|
239
|
+
index += insert_return_and_indent(tokens, index, indent)
|
240
|
+
end
|
241
|
+
|
242
|
+
# キーワードの前で改行
|
243
|
+
if @rule.kw_nl_x.any?{ |kw| token.string.equalsIgnoreCase(kw) }
|
244
|
+
index += insert_return_and_indent(tokens, index, indent)
|
245
|
+
end
|
246
|
+
|
247
|
+
# キーワードの前で改行, インデント+1
|
248
|
+
if @rule.kw_nl_x_plus1_indent.any?{ |kw| token.string.equalsIgnoreCase(kw) }
|
249
|
+
index += insert_return_and_indent(tokens, index, indent + 1)
|
250
|
+
end
|
251
|
+
|
252
|
+
# キーワードの前で改行。indentを強制的に0にする。
|
253
|
+
if (token.string.equalsIgnoreCase("UNION" ) ||
|
254
|
+
token.string.equalsIgnoreCase("INTERSECT") ||
|
255
|
+
token.string.equalsIgnoreCase("EXCEPT" ) )
|
256
|
+
indent -= 2
|
257
|
+
index += insert_return_and_indent(tokens, index , indent)
|
258
|
+
index += insert_return_and_indent(tokens, index + 1, indent)
|
259
|
+
end
|
260
|
+
|
261
|
+
if token.string.equalsIgnoreCase("BETWEEN")
|
262
|
+
encounterBetween = true
|
263
|
+
end
|
264
|
+
|
265
|
+
if token.string.equalsIgnoreCase("AND")
|
266
|
+
# BETWEEN のあとのANDは改行しない。
|
267
|
+
if not encounterBetween
|
268
|
+
index += insert_return_and_indent(tokens, index, indent)
|
269
|
+
end
|
270
|
+
encounterBetween = false
|
271
|
+
end
|
272
|
+
|
273
|
+
elsif (token._type == AnbtSql::TokenConstants::COMMENT) # ****
|
274
|
+
|
275
|
+
if token.string.startsWith("/*")
|
276
|
+
# マルチラインコメントの後に改行を入れる。
|
277
|
+
index += insert_return_and_indent(tokens, index + 1, indent)
|
278
|
+
elsif /^--/ =~ token.string
|
279
|
+
index += insert_return_and_indent(tokens, index + 1, indent)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
prev = token
|
283
|
+
|
284
|
+
index += 1
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
# before: [..., "(", space, "X", space, ")", ...]
|
290
|
+
# after: [..., "(X)", ...]
|
291
|
+
# ただし、これでは "(X)" という一つの symbol トークンになってしまう。
|
292
|
+
# 整形だけが目的ならそれでも良いが、
|
293
|
+
# せっかくなので symbol/X/symbol と分けたい。
|
294
|
+
def special_treatment_for_parenthesis_with_one_element(tokens)
|
295
|
+
(tokens.size - 1).downto(4).each{|index|
|
296
|
+
next if (index >= tokens.size())
|
297
|
+
|
298
|
+
t0 = tokens.get(index )
|
299
|
+
t1 = tokens.get(index - 1)
|
300
|
+
t2 = tokens.get(index - 2)
|
301
|
+
t3 = tokens.get(index - 3)
|
302
|
+
t4 = tokens.get(index - 4)
|
303
|
+
|
304
|
+
if (t4.string. equalsIgnoreCase("(") &&
|
305
|
+
t3.string.trim.equalsIgnoreCase("" ) &&
|
306
|
+
t1.string.trim.equalsIgnoreCase("" ) &&
|
307
|
+
t0.string. equalsIgnoreCase(")") )
|
308
|
+
t4.string = t4.string + t2.string + t0.string
|
309
|
+
tokens.remove(index )
|
310
|
+
tokens.remove(index - 1)
|
311
|
+
tokens.remove(index - 2)
|
312
|
+
tokens.remove(index - 3)
|
313
|
+
end
|
314
|
+
}
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
def format_list(tokens)
|
319
|
+
return [] if tokens.empty?
|
320
|
+
|
321
|
+
# SQLの前後に空白があったら削除する。
|
322
|
+
# Delete space token at first and last of SQL tokens.
|
323
|
+
|
324
|
+
token = tokens.get(0)
|
325
|
+
if (token._type == AnbtSql::TokenConstants::SPACE)
|
326
|
+
tokens.remove(0)
|
327
|
+
end
|
328
|
+
return [] if tokens.empty?
|
329
|
+
|
330
|
+
token = tokens.get(tokens.size() - 1)
|
331
|
+
if token._type == AnbtSql::TokenConstants::SPACE
|
332
|
+
tokens.remove(tokens.size() - 1)
|
333
|
+
end
|
334
|
+
return [] if tokens.empty?
|
335
|
+
|
336
|
+
modify_keyword_case(tokens)
|
337
|
+
remove_symbol_side_space(tokens)
|
338
|
+
concat_operator_for_oracle(tokens)
|
339
|
+
|
340
|
+
encounterBetween = false
|
341
|
+
|
342
|
+
format_list_main_loop(tokens)
|
343
|
+
|
344
|
+
special_treatment_for_parenthesis_with_one_element(tokens)
|
345
|
+
insert_space_between_tokens(tokens)
|
346
|
+
|
347
|
+
return tokens
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
##
|
352
|
+
# index の箇所のトークンの前に挿入します。
|
353
|
+
#
|
354
|
+
# 空白を置き換えた場合:: return 0
|
355
|
+
# 空白を挿入した場合:: return 1
|
356
|
+
def insert_return_and_indent(tokens, index, indent, opt=nil)
|
357
|
+
# 関数内では改行は挿入しない
|
358
|
+
# No linefeed in function.
|
359
|
+
return 0 if (@function_bracket.include?(true))
|
360
|
+
|
361
|
+
begin
|
362
|
+
# 挿入する文字列を作成する。
|
363
|
+
s = "\n"
|
364
|
+
# もし1つ前にシングルラインコメントがあるなら、改行は不要。
|
365
|
+
prevToken = tokens.get(index - 1)
|
366
|
+
|
367
|
+
if (prevToken._type == AnbtSql::TokenConstants::COMMENT &&
|
368
|
+
prevToken.string.startsWith("--"))
|
369
|
+
s = ""
|
370
|
+
end
|
371
|
+
|
372
|
+
# インデントをつける。
|
373
|
+
indent = 0 if indent < 0 ## Java版と異なる
|
374
|
+
s += @rule.indent_string * indent
|
375
|
+
|
376
|
+
# 前後にすでにスペースがあれば、それを置き換える。
|
377
|
+
token = tokens.get(index)
|
378
|
+
if token._type == AnbtSql::TokenConstants::SPACE
|
379
|
+
token.string = s
|
380
|
+
return 0
|
381
|
+
end
|
382
|
+
|
383
|
+
token = tokens.get(index - 1)
|
384
|
+
if token._type == AnbtSql::TokenConstants::SPACE
|
385
|
+
token.string = s
|
386
|
+
return 0
|
387
|
+
end
|
388
|
+
|
389
|
+
# 前後になければ、新たにスペースを追加する。
|
390
|
+
tokens.add(index,
|
391
|
+
AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE, s)
|
392
|
+
)
|
393
|
+
return 1
|
394
|
+
rescue IndexOutOfBoundsException => e
|
395
|
+
if $DEBUG
|
396
|
+
$stderr.puts e.message, e.backtrace
|
397
|
+
$stderr.puts "tokens: "
|
398
|
+
tokens.each_with_index{|t,i|
|
399
|
+
$stderr.puts "index=%d: %s" % [i, t.inspect]
|
400
|
+
}
|
401
|
+
$stderr.puts "index/size: %d/%d / indent: %d / opt: %s" % [index, tokens.size, indent, opt]
|
402
|
+
end
|
403
|
+
return 0
|
404
|
+
rescue => e
|
405
|
+
raise e
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|