-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
SqlIdentifier.java
395 lines (348 loc) · 13 KB
/
SqlIdentifier.java
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.sql;
import org.apache.calcite.rel.type.DynamicRecordType;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.util.SqlVisitor;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlQualified;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.Pure;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A <code>SqlIdentifier</code> is an identifier, possibly compound.
*/
public class SqlIdentifier extends SqlNode {
/** An identifier for star, "*".
*
* @see SqlNodeList#SINGLETON_STAR */
public static final SqlIdentifier STAR = star(SqlParserPos.ZERO);
//~ Instance fields --------------------------------------------------------
/**
* Array of the components of this compound identifier.
*
* <p>The empty string represents the wildcard "*",
* to distinguish it from a real "*" (presumably specified using quotes).
*
* <p>It's convenient to have this member public, and it's convenient to
* have this member not-final, but it's a shame it's public and not-final.
* If you assign to this member, please use
* {@link #setNames(java.util.List, java.util.List)}.
* And yes, we'd like to make identifiers immutable one day.
*/
public ImmutableList<String> names;
/**
* This identifier's collation (if any).
*/
final @Nullable SqlCollation collation;
/**
* A list of the positions of the components of compound identifiers.
*/
protected @Nullable ImmutableList<SqlParserPos> componentPositions;
//~ Constructors -----------------------------------------------------------
/**
* Creates a compound identifier, for example <code>foo.bar</code>.
*
* @param names Parts of the identifier, length ≥ 1
*/
public SqlIdentifier(
List<String> names,
@Nullable SqlCollation collation,
SqlParserPos pos,
@Nullable List<SqlParserPos> componentPositions) {
super(pos);
this.names = ImmutableList.copyOf(names);
this.collation = collation;
this.componentPositions = componentPositions == null ? null
: ImmutableList.copyOf(componentPositions);
for (String name : names) {
assert name != null;
}
}
public SqlIdentifier(List<String> names, SqlParserPos pos) {
this(names, null, pos, null);
}
/**
* Creates a simple identifier, for example <code>foo</code>, with a
* collation.
*/
public SqlIdentifier(
String name,
@Nullable SqlCollation collation,
SqlParserPos pos) {
this(ImmutableList.of(name), collation, pos, null);
}
/**
* Creates a simple identifier, for example <code>foo</code>.
*/
public SqlIdentifier(
String name,
SqlParserPos pos) {
this(ImmutableList.of(name), null, pos, null);
}
/** Creates an identifier that is a singleton wildcard star. */
public static SqlIdentifier star(SqlParserPos pos) {
return star(ImmutableList.of(""), pos, ImmutableList.of(pos));
}
/** Creates an identifier that ends in a wildcard star. */
public static SqlIdentifier star(List<String> names, SqlParserPos pos,
List<SqlParserPos> componentPositions) {
return new SqlIdentifier(
Util.transform(names, s -> s.equals("*") ? "" : s), null, pos,
componentPositions);
}
//~ Methods ----------------------------------------------------------------
@Override public SqlKind getKind() {
return SqlKind.IDENTIFIER;
}
@Override public SqlNode clone(SqlParserPos pos) {
return new SqlIdentifier(names, collation, pos, componentPositions);
}
@Override public String toString() {
return getString(names);
}
/** Converts a list of strings to a qualified identifier. */
public static String getString(List<String> names) {
return Util.sepList(toStar(names), ".");
}
/** Converts empty strings in a list of names to stars. */
public static List<String> toStar(List<String> names) {
return Util.transform(names, s -> s.equals("") ? "*" : s);
}
/**
* Modifies the components of this identifier and their positions.
*
* @param names Names of components
* @param poses Positions of components
*/
public void setNames(List<String> names, @Nullable List<SqlParserPos> poses) {
this.names = ImmutableList.copyOf(names);
this.componentPositions = poses == null ? null
: ImmutableList.copyOf(poses);
}
/** Returns an identifier that is the same as this except one modified name.
* Does not modify this identifier. */
public SqlIdentifier setName(int i, String name) {
if (!names.get(i).equals(name)) {
String[] nameArray = names.toArray(new String[0]);
nameArray[i] = name;
return new SqlIdentifier(ImmutableList.copyOf(nameArray), collation, pos,
componentPositions);
} else {
return this;
}
}
/** Returns an identifier that is the same as this except with a component
* added at a given position. Does not modify this identifier. */
public SqlIdentifier add(int i, String name, SqlParserPos pos) {
final List<String> names2 = new ArrayList<>(names);
names2.add(i, name);
final List<SqlParserPos> pos2;
if (componentPositions == null) {
pos2 = null;
} else {
pos2 = new ArrayList<>(componentPositions);
pos2.add(i, pos);
}
return new SqlIdentifier(names2, collation, pos, pos2);
}
/**
* Returns the position of the <code>i</code>th component of a compound
* identifier, or the position of the whole identifier if that information
* is not present.
*
* @param i Ordinal of component.
* @return Position of i'th component
*/
public SqlParserPos getComponentParserPosition(int i) {
assert (i >= 0) && (i < names.size());
return (componentPositions == null) ? getParserPosition()
: componentPositions.get(i);
}
/**
* Copies names and components from another identifier. Does not modify the
* cross-component parser position.
*
* @param other identifier from which to copy
*/
public void assignNamesFrom(SqlIdentifier other) {
setNames(other.names, other.componentPositions);
}
/**
* Creates an identifier which contains only the <code>ordinal</code>th
* component of this compound identifier. It will have the correct
* {@link SqlParserPos}, provided that detailed position information is
* available.
*/
public SqlIdentifier getComponent(int ordinal) {
return getComponent(ordinal, ordinal + 1);
}
public SqlIdentifier getComponent(int from, int to) {
final SqlParserPos pos;
final ImmutableList<SqlParserPos> pos2;
if (componentPositions == null) {
pos2 = null;
pos = this.pos;
} else {
pos2 = componentPositions.subList(from, to);
pos = SqlParserPos.sum(pos2);
}
return new SqlIdentifier(names.subList(from, to), collation, pos, pos2);
}
/**
* Creates an identifier that consists of this identifier plus a name segment.
* Does not modify this identifier.
*/
public SqlIdentifier plus(String name, SqlParserPos pos) {
final ImmutableList<String> names =
ImmutableList.<String>builder().addAll(this.names).add(name).build();
final ImmutableList<SqlParserPos> componentPositions;
final SqlParserPos pos2;
ImmutableList<SqlParserPos> thisComponentPositions = this.componentPositions;
if (thisComponentPositions != null) {
final ImmutableList.Builder<SqlParserPos> builder =
ImmutableList.builder();
componentPositions =
builder.addAll(thisComponentPositions).add(pos).build();
pos2 = SqlParserPos.sum(builder.add(this.pos).build());
} else {
componentPositions = null;
pos2 = pos;
}
return new SqlIdentifier(names, collation, pos2, componentPositions);
}
/**
* Creates an identifier that consists of this identifier plus a wildcard star.
* Does not modify this identifier.
*/
public SqlIdentifier plusStar() {
final SqlIdentifier id = this.plus("*", SqlParserPos.ZERO);
return new SqlIdentifier(
id.names.stream().map(s -> s.equals("*") ? "" : s)
.collect(Util.toImmutableList()),
null, id.pos, id.componentPositions);
}
/** Creates an identifier that consists of all but the last {@code n}
* name segments of this one. */
public SqlIdentifier skipLast(int n) {
return getComponent(0, names.size() - n);
}
@Override public void unparse(
SqlWriter writer,
int leftPrec,
int rightPrec) {
SqlUtil.unparseSqlIdentifierSyntax(writer, this, false);
}
@Override public void validate(SqlValidator validator, SqlValidatorScope scope) {
validator.validateIdentifier(this, scope);
}
@Override public void validateExpr(SqlValidator validator, SqlValidatorScope scope) {
// First check for builtin functions which don't have parentheses,
// like "LOCALTIME".
final SqlCall call = validator.makeNullaryCall(this);
if (call != null) {
validator.validateCall(call, scope);
return;
}
validator.validateIdentifier(this, scope);
}
@Override public boolean equalsDeep(@Nullable SqlNode node, Litmus litmus) {
if (!(node instanceof SqlIdentifier)) {
return litmus.fail("{} != {}", this, node);
}
SqlIdentifier that = (SqlIdentifier) node;
if (this.names.size() != that.names.size()) {
return litmus.fail("{} != {}", this, node);
}
for (int i = 0; i < names.size(); i++) {
if (!this.names.get(i).equals(that.names.get(i))) {
return litmus.fail("{} != {}", this, node);
}
}
return litmus.succeed();
}
@Override public <R> R accept(SqlVisitor<R> visitor) {
return visitor.visit(this);
}
@Pure
public @Nullable SqlCollation getCollation() {
return collation;
}
public String getSimple() {
assert names.size() == 1;
return names.get(0);
}
/** Returns the simple names in a list of identifiers.
* Assumes that the list consists of are not-null, simple identifiers. */
public static List<String> simpleNames(List<? extends SqlNode> list) {
return Util.transform(list, n -> ((SqlIdentifier) n).getSimple());
}
/** Returns the simple names in a iterable of identifiers.
* Assumes that the iterable consists of not-null, simple identifiers. */
public static Iterable<String> simpleNames(Iterable<? extends SqlNode> list) {
return Util.transform(list, n -> ((SqlIdentifier) n).getSimple());
}
/**
* Returns whether this identifier is a star, such as "*" or "foo.bar.*".
*/
public boolean isStar() {
return Util.last(names).equals("");
}
/**
* Returns whether this is a simple identifier. "FOO" is simple; "*",
* "FOO.*" and "FOO.BAR" are not.
*/
public boolean isSimple() {
return names.size() == 1 && !isStar();
}
/**
* Returns whether the {@code i}th component of a compound identifier is
* quoted.
*
* @param i Ordinal of component
* @return Whether i'th component is quoted
*/
public boolean isComponentQuoted(int i) {
return componentPositions != null
&& componentPositions.get(i).isQuoted();
}
@Override public SqlMonotonicity getMonotonicity(@Nullable SqlValidatorScope scope) {
// for "star" column, whether it's static or dynamic return not_monotonic directly.
if (Util.last(names).equals("") || DynamicRecordType.isDynamicStarColName(Util.last(names))) {
return SqlMonotonicity.NOT_MONOTONIC;
}
Objects.requireNonNull(scope, "scope");
// First check for builtin functions which don't have parentheses,
// like "LOCALTIME".
final SqlValidator validator = scope.getValidator();
final SqlCall call = validator.makeNullaryCall(this);
if (call != null) {
return call.getMonotonicity(scope);
}
final SqlQualified qualified = scope.fullyQualify(this);
assert qualified.namespace != null : "namespace must not be null in " + qualified;
final SqlIdentifier fqId = qualified.identifier;
return qualified.namespace.resolve().getMonotonicity(Util.last(fqId.names));
}
}