16
16
*/
17
17
package org .apache .calcite .adapter .mongodb ;
18
18
19
+ import org .apache .calcite .avatica .util .ByteString ;
19
20
import org .apache .calcite .avatica .util .DateTimeUtils ;
20
21
import org .apache .calcite .linq4j .Enumerator ;
21
22
import org .apache .calcite .linq4j .function .Function1 ;
22
23
import org .apache .calcite .linq4j .tree .Primitive ;
23
24
24
25
import com .mongodb .client .MongoCursor ;
25
26
27
+ import org .bson .BsonTimestamp ;
26
28
import org .bson .Document ;
29
+ import org .bson .types .Binary ;
30
+ import org .bson .types .Decimal128 ;
27
31
import org .checkerframework .checker .nullness .qual .Nullable ;
28
32
33
+ import java .math .BigDecimal ;
29
34
import java .util .Date ;
30
35
import java .util .Iterator ;
31
36
import java .util .List ;
32
37
import java .util .Map ;
33
38
39
+ import static java .lang .String .format ;
40
+
34
41
/** Enumerator that reads from a MongoDB collection. */
35
42
class MongoEnumerator implements Enumerator <Object > {
36
43
private final Iterator <Document > cursor ;
@@ -89,7 +96,7 @@ static Function1<Document, Map> mapGetter() {
89
96
/** Returns a function that projects a single field. */
90
97
static Function1 <Document , Object > singletonGetter (final String fieldName ,
91
98
final Class fieldClass ) {
92
- return a0 -> convert (a0 .get (fieldName ), fieldClass );
99
+ return a0 -> convert (fieldName , a0 .get (fieldName ), fieldClass );
93
100
}
94
101
95
102
/** Returns a function that projects fields.
@@ -103,7 +110,7 @@ static Function1<Document, Object[]> listGetter(
103
110
for (int i = 0 ; i < fields .size (); i ++) {
104
111
final Map .Entry <String , Class > field = fields .get (i );
105
112
final String name = field .getKey ();
106
- objects [i ] = convert (a0 .get (name ), field .getValue ());
113
+ objects [i ] = convert (name , a0 .get (name ), field .getValue ());
107
114
}
108
115
return objects ;
109
116
};
@@ -119,8 +126,34 @@ static Function1<Document, Object> getter(
119
126
: (Function1 ) listGetter (fields );
120
127
}
121
128
129
+ /**
130
+ * Converts the given object to a specific runtime type based on the provided class.
131
+ *
132
+ * @param fieldName The name of the field being processed, used for error reporting if
133
+ * conversion fails.
134
+ * @param o The object to be converted. If `null`, the method returns `null` immediately.
135
+ * @param clazz The target class to which the object `o` should be converted.
136
+ * @return The converted object as an instance of the specified `clazz`, or `null` if `o` is
137
+ * `null`.
138
+ *
139
+ * @throws IllegalArgumentException if the object `o` cannot be converted to the desired
140
+ * `clazz` type, including a message indicating the field name, expected data type, and the
141
+ * invalid value.
142
+ *
143
+ * <h3>Conversion Details:
144
+ *
145
+ * <p>If the target type is one of the following, the method performs specific conversions:
146
+ * <ul>
147
+ * <li>`Long`: Converts a `Date` or `BsonTimestamp` object into the respective epoch time
148
+ * (milliseconds).
149
+ * <li>`BigDecimal`: Converts a `Decimal128` object into a `BigDecimal` instance.
150
+ * <li>`String`: Converts arrays to string and uses `String.valueOf(o)` for other objects.
151
+ * <li>`ByteString`: Converts a `Binary` object into a `ByteString` instance.
152
+ * </ul>
153
+ *
154
+ */
122
155
@ SuppressWarnings ("JavaUtilDate" )
123
- private static Object convert (Object o , Class clazz ) {
156
+ private static Object convert (String fieldName , Object o , Class clazz ) {
124
157
if (o == null ) {
125
158
return null ;
126
159
}
@@ -133,14 +166,41 @@ private static Object convert(Object o, Class clazz) {
133
166
if (clazz .isInstance (o )) {
134
167
return o ;
135
168
}
136
- if (o instanceof Date && clazz == Long .class ) {
137
- o = ((Date ) o ).getTime ();
138
- } else if (o instanceof Date && primitive != null ) {
139
- o = ((Date ) o ).getTime () / DateTimeUtils .MILLIS_PER_DAY ;
169
+
170
+ if (clazz == Long .class ) {
171
+ if (o instanceof Date ) {
172
+ return ((Date ) o ).getTime ();
173
+ } else if (o instanceof BsonTimestamp ) {
174
+ return ((BsonTimestamp ) o ).getTime () * DateTimeUtils .MILLIS_PER_SECOND ;
175
+ }
176
+ } else if (clazz == BigDecimal .class ) {
177
+ if (o instanceof Decimal128 ) {
178
+ return new BigDecimal (((Decimal128 ) o ).toString ());
179
+ }
180
+ } else if (clazz == String .class ) {
181
+ if (o .getClass ().isArray ()) {
182
+ return Primitive .OTHER .arrayToString (o );
183
+ } else {
184
+ return String .valueOf (o );
185
+ }
186
+ } else if (clazz == ByteString .class ) {
187
+ if (o instanceof Binary ) {
188
+ return new ByteString (((Binary ) o ).getData ());
189
+ }
140
190
}
141
- if (o instanceof Number && primitive != null ) {
142
- return primitive .number ((Number ) o );
191
+
192
+ if (primitive != null ) {
193
+ if (o instanceof String ) {
194
+ return primitive .parse ((String ) o );
195
+ } else if (o instanceof Number ) {
196
+ return primitive .number ((Number ) o );
197
+ } else if (o instanceof Date ) {
198
+ return primitive .number (((Date ) o ).getTime () / DateTimeUtils .MILLIS_PER_DAY );
199
+ }
143
200
}
144
- return o ;
201
+
202
+ throw new IllegalArgumentException (
203
+ format ("Invalid field: '%s'. The dataType '%s' is invalid for '%s'." , fieldName ,
204
+ clazz .getSimpleName (), o ));
145
205
}
146
206
}
0 commit comments