@@ -21,20 +21,21 @@ import org.partiql.value.PartiQLValueType
21
21
*/
22
22
@OptIn(PartiQLValueExperimental ::class , FnExperimental ::class )
23
23
internal class ExprCallDynamic (
24
- private val candidates : List <Candidate >,
24
+ candidates : Array <Candidate >,
25
25
private val args : Array <Operator .Expr >
26
26
) : Operator.Expr {
27
27
28
+ private val candidateIndex = CandidateIndex .All (candidates)
29
+
28
30
override fun eval (env : Environment ): PartiQLValue {
29
31
val actualArgs = args.map { it.eval(env) }.toTypedArray()
30
- candidates.forEach { candidate ->
31
- if (candidate.matches(actualArgs)) {
32
- return candidate.eval(actualArgs, env)
33
- }
32
+ val actualTypes = actualArgs.map { it.type }
33
+ candidateIndex.get(actualTypes)?.let {
34
+ return it.eval(actualArgs, env)
34
35
}
35
36
val errorString = buildString {
36
37
val argString = actualArgs.joinToString(" , " )
37
- append(" Could not dynamically find function for arguments $argString in $candidates ." )
38
+ append(" Could not dynamically find function ( ${candidateIndex.name} ) for arguments $argString ." )
38
39
}
39
40
throw TypeCheckException (errorString)
40
41
}
@@ -47,13 +48,11 @@ internal class ExprCallDynamic(
47
48
*
48
49
* @see ExprCallDynamic
49
50
*/
50
- internal class Candidate (
51
+ data class Candidate (
51
52
val fn : Fn ,
52
53
val coercions : Array <Ref .Cast ?>
53
54
) {
54
55
55
- private val signatureParameters = fn.signature.parameters.map { it.type }.toTypedArray()
56
-
57
56
fun eval (originalArgs : Array <PartiQLValue >, env : Environment ): PartiQLValue {
58
57
val args = originalArgs.mapIndexed { i, arg ->
59
58
when (val c = coercions[i]) {
@@ -63,32 +62,156 @@ internal class ExprCallDynamic(
63
62
}.toTypedArray()
64
63
return fn.invoke(args)
65
64
}
65
+ }
66
+
67
+ private sealed interface CandidateIndex {
68
+
69
+ public fun get (args : List <PartiQLValueType >): Candidate ?
70
+
71
+ /* *
72
+ * Preserves the original ordering of the passed-in candidates while making it faster to lookup matching
73
+ * functions. Utilizes both [Direct] and [Indirect].
74
+ *
75
+ * Say a user passes in the following ordered candidates:
76
+ * [
77
+ * foo(int16, int16) -> int16,
78
+ * foo(int32, int32) -> int32,
79
+ * foo(int64, int64) -> int64,
80
+ * foo(string, string) -> string,
81
+ * foo(struct, struct) -> struct,
82
+ * foo(numeric, numeric) -> numeric,
83
+ * foo(int64, dynamic) -> dynamic,
84
+ * foo(struct, dynamic) -> dynamic,
85
+ * foo(bool, bool) -> bool
86
+ * ]
87
+ *
88
+ * With the above candidates, the [CandidateIndex.All] will maintain the original ordering by utilizing:
89
+ * - [CandidateIndex.Direct] to match hashable runtime types
90
+ * - [CandidateIndex.Indirect] to match the dynamic type
91
+ *
92
+ * For the above example, the internal representation of [CandidateIndex.All] is a list of
93
+ * [CandidateIndex.Direct] and [CandidateIndex.Indirect] that looks like:
94
+ * ALL listOf(
95
+ * DIRECT hashMap(
96
+ * [int16, int16] --> foo(int16, int16) -> int16,
97
+ * [int32, int32] --> foo(int32, int32) -> int32,
98
+ * [int64, int64] --> foo(int64, int64) -> int64
99
+ * [string, string] --> foo(string, string) -> string,
100
+ * [struct, struct] --> foo(struct, struct) -> struct,
101
+ * [numeric, numeric] --> foo(numeric, numeric) -> numeric
102
+ * ),
103
+ * INDIRECT listOf(
104
+ * foo(int64, dynamic) -> dynamic,
105
+ * foo(struct, dynamic) -> dynamic
106
+ * ),
107
+ * DIRECT hashMap(
108
+ * [bool, bool] --> foo(bool, bool) -> bool
109
+ * )
110
+ * )
111
+ *
112
+ * @param candidates
113
+ */
114
+ class All (
115
+ candidates : Array <Candidate >,
116
+ ) : CandidateIndex {
117
+
118
+ private val lookups: List <CandidateIndex >
119
+ internal val name: String = candidates.first().fn.signature.name
66
120
67
- internal fun matches ( inputs : Array < PartiQLValue >): Boolean {
68
- for (i in inputs.indices) {
69
- val inputType = inputs[i].type
70
- val parameterType = signatureParameters[i]
71
- val c = coercions[i]
72
- when (c) {
73
- // coercion might be null if one of the following is true
74
- // Function parameter is ANY,
75
- // Input type is null
76
- // input type is the same as function parameter
77
- null -> {
78
- if ( ! (inputType == parameterType || inputType == PartiQLValueType . NULL || parameterType == PartiQLValueType . ANY )) {
79
- return false
121
+ init {
122
+ val lookupsMutable = mutableListOf< CandidateIndex >()
123
+ val accumulator = mutableListOf< Pair < List < PartiQLValueType >, Candidate >> ()
124
+
125
+ // Indicates that we are currently processing dynamic candidates that accept ANY.
126
+ var activelyProcessingAny = true
127
+
128
+ candidates.forEach { candidate ->
129
+ // Gather the input types to the dynamic invocation
130
+ val lookupTypes = candidate.coercions.mapIndexed { index, cast ->
131
+ when (cast) {
132
+ null -> candidate.fn.signature.parameters[index].type
133
+ else -> cast.input
80
134
}
81
135
}
82
- else -> {
83
- // checking the input type is expected by the coercion
84
- if (inputType != c.input) return false
85
- // checking the result is expected by the function signature
86
- // this should branch should never be reached, but leave it here for clarity
87
- if (c.target != parameterType) error(" Internal Error: Cast Target does not match Function Parameter" )
136
+ val parametersIncludeAny = lookupTypes.any { it == PartiQLValueType .ANY }
137
+ // A way to simplify logic further below. If it's empty, add something and set the processing type.
138
+ if (accumulator.isEmpty()) {
139
+ activelyProcessingAny = parametersIncludeAny
140
+ accumulator.add(lookupTypes to candidate)
141
+ return @forEach
142
+ }
143
+ when (parametersIncludeAny) {
144
+ true -> when (activelyProcessingAny) {
145
+ true -> accumulator.add(lookupTypes to candidate)
146
+ false -> {
147
+ activelyProcessingAny = true
148
+ lookupsMutable.add(Direct .of(accumulator.toList()))
149
+ accumulator.clear()
150
+ accumulator.add(lookupTypes to candidate)
151
+ }
152
+ }
153
+ false -> when (activelyProcessingAny) {
154
+ false -> accumulator.add(lookupTypes to candidate)
155
+ true -> {
156
+ activelyProcessingAny = false
157
+ lookupsMutable.add(Indirect (accumulator.toList()))
158
+ accumulator.clear()
159
+ accumulator.add(lookupTypes to candidate)
160
+ }
161
+ }
162
+ }
163
+ }
164
+ // Add any remaining candidates (that we didn't submit due to not ending while switching)
165
+ when (accumulator.isEmpty()) {
166
+ true -> { /* Do nothing! */ }
167
+ false -> when (activelyProcessingAny) {
168
+ true -> lookupsMutable.add(Indirect (accumulator.toList()))
169
+ false -> lookupsMutable.add(Direct .of(accumulator.toList()))
170
+ }
171
+ }
172
+ this .lookups = lookupsMutable
173
+ }
174
+
175
+ override fun get (args : List <PartiQLValueType >): Candidate ? {
176
+ return this .lookups.firstNotNullOfOrNull { it.get(args) }
177
+ }
178
+ }
179
+
180
+ /* *
181
+ * An O(1) structure to quickly find directly matching dynamic candidates. This is specifically used for runtime
182
+ * types that can be matched directly. AKA int32, int64, etc. This does NOT include [PartiQLValueType.ANY].
183
+ */
184
+ data class Direct private constructor(val directCandidates : HashMap <List <PartiQLValueType >, Candidate >) : CandidateIndex {
185
+
186
+ companion object {
187
+ internal fun of (candidates : List <Pair <List <PartiQLValueType >, Candidate >>): Direct {
188
+ val candidateMap = java.util.HashMap <List <PartiQLValueType >, Candidate > ()
189
+ candidateMap.putAll(candidates)
190
+ return Direct (candidateMap)
191
+ }
192
+ }
193
+
194
+ override fun get (args : List <PartiQLValueType >): Candidate ? {
195
+ return directCandidates[args]
196
+ }
197
+ }
198
+
199
+ /* *
200
+ * Holds all candidates that expect a [PartiQLValueType.ANY] on input. This maintains the original
201
+ * precedence order.
202
+ */
203
+ data class Indirect (private val candidates : List <Pair <List <PartiQLValueType >, Candidate >>) : CandidateIndex {
204
+ override fun get (args : List <PartiQLValueType >): Candidate ? {
205
+ candidates.forEach { (types, candidate) ->
206
+ for (i in args.indices) {
207
+ if (args[i] != types[i] && types[i] != PartiQLValueType .ANY ) {
208
+ return @forEach
209
+ }
88
210
}
211
+ return candidate
89
212
}
213
+ return null
90
214
}
91
- return true
92
215
}
93
216
}
94
217
}
0 commit comments