datetime_functions_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. package engine
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "testing"
  7. "time"
  8. "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
  9. )
  10. func TestDateTimeFunctions(t *testing.T) {
  11. engine := NewTestSQLEngine()
  12. t.Run("CURRENT_DATE function tests", func(t *testing.T) {
  13. before := time.Now()
  14. result, err := engine.CurrentDate()
  15. after := time.Now()
  16. if err != nil {
  17. t.Errorf("CurrentDate failed: %v", err)
  18. }
  19. if result == nil {
  20. t.Errorf("CurrentDate returned nil result")
  21. return
  22. }
  23. stringVal, ok := result.Kind.(*schema_pb.Value_StringValue)
  24. if !ok {
  25. t.Errorf("CurrentDate should return string value, got %T", result.Kind)
  26. return
  27. }
  28. // Check format (YYYY-MM-DD) with tolerance for midnight boundary crossings
  29. beforeDate := before.Format("2006-01-02")
  30. afterDate := after.Format("2006-01-02")
  31. if stringVal.StringValue != beforeDate && stringVal.StringValue != afterDate {
  32. t.Errorf("Expected current date %s or %s (due to potential midnight boundary), got %s",
  33. beforeDate, afterDate, stringVal.StringValue)
  34. }
  35. })
  36. t.Run("CURRENT_TIMESTAMP function tests", func(t *testing.T) {
  37. before := time.Now()
  38. result, err := engine.CurrentTimestamp()
  39. after := time.Now()
  40. if err != nil {
  41. t.Errorf("CurrentTimestamp failed: %v", err)
  42. }
  43. if result == nil {
  44. t.Errorf("CurrentTimestamp returned nil result")
  45. return
  46. }
  47. timestampVal, ok := result.Kind.(*schema_pb.Value_TimestampValue)
  48. if !ok {
  49. t.Errorf("CurrentTimestamp should return timestamp value, got %T", result.Kind)
  50. return
  51. }
  52. timestamp := time.UnixMicro(timestampVal.TimestampValue.TimestampMicros)
  53. // Check that timestamp is within reasonable range with small tolerance buffer
  54. // Allow for small timing variations, clock precision differences, and NTP adjustments
  55. tolerance := 100 * time.Millisecond
  56. beforeWithTolerance := before.Add(-tolerance)
  57. afterWithTolerance := after.Add(tolerance)
  58. if timestamp.Before(beforeWithTolerance) || timestamp.After(afterWithTolerance) {
  59. t.Errorf("Timestamp %v should be within tolerance of %v to %v (tolerance: %v)",
  60. timestamp, before, after, tolerance)
  61. }
  62. })
  63. t.Run("NOW function tests", func(t *testing.T) {
  64. result, err := engine.Now()
  65. if err != nil {
  66. t.Errorf("Now failed: %v", err)
  67. }
  68. if result == nil {
  69. t.Errorf("Now returned nil result")
  70. return
  71. }
  72. // Should return same type as CurrentTimestamp
  73. _, ok := result.Kind.(*schema_pb.Value_TimestampValue)
  74. if !ok {
  75. t.Errorf("Now should return timestamp value, got %T", result.Kind)
  76. }
  77. })
  78. t.Run("CURRENT_TIME function tests", func(t *testing.T) {
  79. result, err := engine.CurrentTime()
  80. if err != nil {
  81. t.Errorf("CurrentTime failed: %v", err)
  82. }
  83. if result == nil {
  84. t.Errorf("CurrentTime returned nil result")
  85. return
  86. }
  87. stringVal, ok := result.Kind.(*schema_pb.Value_StringValue)
  88. if !ok {
  89. t.Errorf("CurrentTime should return string value, got %T", result.Kind)
  90. return
  91. }
  92. // Check format (HH:MM:SS)
  93. if len(stringVal.StringValue) != 8 || stringVal.StringValue[2] != ':' || stringVal.StringValue[5] != ':' {
  94. t.Errorf("CurrentTime should return HH:MM:SS format, got %s", stringVal.StringValue)
  95. }
  96. })
  97. }
  98. func TestExtractFunction(t *testing.T) {
  99. engine := NewTestSQLEngine()
  100. // Create a test timestamp: 2023-06-15 14:30:45
  101. // Use local time to avoid timezone conversion issues
  102. testTime := time.Date(2023, 6, 15, 14, 30, 45, 0, time.Local)
  103. testTimestamp := &schema_pb.Value{
  104. Kind: &schema_pb.Value_TimestampValue{
  105. TimestampValue: &schema_pb.TimestampValue{
  106. TimestampMicros: testTime.UnixMicro(),
  107. },
  108. },
  109. }
  110. tests := []struct {
  111. name string
  112. part DatePart
  113. value *schema_pb.Value
  114. expected int64
  115. expectErr bool
  116. }{
  117. {
  118. name: "Extract YEAR",
  119. part: PartYear,
  120. value: testTimestamp,
  121. expected: 2023,
  122. expectErr: false,
  123. },
  124. {
  125. name: "Extract MONTH",
  126. part: PartMonth,
  127. value: testTimestamp,
  128. expected: 6,
  129. expectErr: false,
  130. },
  131. {
  132. name: "Extract DAY",
  133. part: PartDay,
  134. value: testTimestamp,
  135. expected: 15,
  136. expectErr: false,
  137. },
  138. {
  139. name: "Extract HOUR",
  140. part: PartHour,
  141. value: testTimestamp,
  142. expected: 14,
  143. expectErr: false,
  144. },
  145. {
  146. name: "Extract MINUTE",
  147. part: PartMinute,
  148. value: testTimestamp,
  149. expected: 30,
  150. expectErr: false,
  151. },
  152. {
  153. name: "Extract SECOND",
  154. part: PartSecond,
  155. value: testTimestamp,
  156. expected: 45,
  157. expectErr: false,
  158. },
  159. {
  160. name: "Extract QUARTER from June",
  161. part: PartQuarter,
  162. value: testTimestamp,
  163. expected: 2, // June is in Q2
  164. expectErr: false,
  165. },
  166. {
  167. name: "Extract from string date",
  168. part: PartYear,
  169. value: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "2023-06-15"}},
  170. expected: 2023,
  171. expectErr: false,
  172. },
  173. {
  174. name: "Extract from Unix timestamp",
  175. part: PartYear,
  176. value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: testTime.Unix()}},
  177. expected: 2023,
  178. expectErr: false,
  179. },
  180. {
  181. name: "Extract from null value",
  182. part: PartYear,
  183. value: nil,
  184. expected: 0,
  185. expectErr: true,
  186. },
  187. {
  188. name: "Extract invalid part",
  189. part: DatePart("INVALID"),
  190. value: testTimestamp,
  191. expected: 0,
  192. expectErr: true,
  193. },
  194. {
  195. name: "Extract from invalid string",
  196. part: PartYear,
  197. value: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "invalid-date"}},
  198. expected: 0,
  199. expectErr: true,
  200. },
  201. }
  202. for _, tt := range tests {
  203. t.Run(tt.name, func(t *testing.T) {
  204. result, err := engine.Extract(tt.part, tt.value)
  205. if tt.expectErr {
  206. if err == nil {
  207. t.Errorf("Expected error but got none")
  208. }
  209. return
  210. }
  211. if err != nil {
  212. t.Errorf("Unexpected error: %v", err)
  213. return
  214. }
  215. if result == nil {
  216. t.Errorf("Extract returned nil result")
  217. return
  218. }
  219. intVal, ok := result.Kind.(*schema_pb.Value_Int64Value)
  220. if !ok {
  221. t.Errorf("Extract should return int64 value, got %T", result.Kind)
  222. return
  223. }
  224. if intVal.Int64Value != tt.expected {
  225. t.Errorf("Expected %d, got %d", tt.expected, intVal.Int64Value)
  226. }
  227. })
  228. }
  229. }
  230. func TestDateTruncFunction(t *testing.T) {
  231. engine := NewTestSQLEngine()
  232. // Create a test timestamp: 2023-06-15 14:30:45.123456
  233. testTime := time.Date(2023, 6, 15, 14, 30, 45, 123456000, time.Local) // nanoseconds
  234. testTimestamp := &schema_pb.Value{
  235. Kind: &schema_pb.Value_TimestampValue{
  236. TimestampValue: &schema_pb.TimestampValue{
  237. TimestampMicros: testTime.UnixMicro(),
  238. },
  239. },
  240. }
  241. tests := []struct {
  242. name string
  243. precision string
  244. value *schema_pb.Value
  245. expectErr bool
  246. expectedCheck func(result time.Time) bool // Custom check function
  247. }{
  248. {
  249. name: "Truncate to second",
  250. precision: "second",
  251. value: testTimestamp,
  252. expectErr: false,
  253. expectedCheck: func(result time.Time) bool {
  254. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 15 &&
  255. result.Hour() == 14 && result.Minute() == 30 && result.Second() == 45 &&
  256. result.Nanosecond() == 0
  257. },
  258. },
  259. {
  260. name: "Truncate to minute",
  261. precision: "minute",
  262. value: testTimestamp,
  263. expectErr: false,
  264. expectedCheck: func(result time.Time) bool {
  265. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 15 &&
  266. result.Hour() == 14 && result.Minute() == 30 && result.Second() == 0 &&
  267. result.Nanosecond() == 0
  268. },
  269. },
  270. {
  271. name: "Truncate to hour",
  272. precision: "hour",
  273. value: testTimestamp,
  274. expectErr: false,
  275. expectedCheck: func(result time.Time) bool {
  276. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 15 &&
  277. result.Hour() == 14 && result.Minute() == 0 && result.Second() == 0 &&
  278. result.Nanosecond() == 0
  279. },
  280. },
  281. {
  282. name: "Truncate to day",
  283. precision: "day",
  284. value: testTimestamp,
  285. expectErr: false,
  286. expectedCheck: func(result time.Time) bool {
  287. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 15 &&
  288. result.Hour() == 0 && result.Minute() == 0 && result.Second() == 0 &&
  289. result.Nanosecond() == 0
  290. },
  291. },
  292. {
  293. name: "Truncate to month",
  294. precision: "month",
  295. value: testTimestamp,
  296. expectErr: false,
  297. expectedCheck: func(result time.Time) bool {
  298. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 1 &&
  299. result.Hour() == 0 && result.Minute() == 0 && result.Second() == 0 &&
  300. result.Nanosecond() == 0
  301. },
  302. },
  303. {
  304. name: "Truncate to quarter",
  305. precision: "quarter",
  306. value: testTimestamp,
  307. expectErr: false,
  308. expectedCheck: func(result time.Time) bool {
  309. // June (month 6) should truncate to April (month 4) - start of Q2
  310. return result.Year() == 2023 && result.Month() == 4 && result.Day() == 1 &&
  311. result.Hour() == 0 && result.Minute() == 0 && result.Second() == 0 &&
  312. result.Nanosecond() == 0
  313. },
  314. },
  315. {
  316. name: "Truncate to year",
  317. precision: "year",
  318. value: testTimestamp,
  319. expectErr: false,
  320. expectedCheck: func(result time.Time) bool {
  321. return result.Year() == 2023 && result.Month() == 1 && result.Day() == 1 &&
  322. result.Hour() == 0 && result.Minute() == 0 && result.Second() == 0 &&
  323. result.Nanosecond() == 0
  324. },
  325. },
  326. {
  327. name: "Truncate with plural precision",
  328. precision: "minutes", // Test plural form
  329. value: testTimestamp,
  330. expectErr: false,
  331. expectedCheck: func(result time.Time) bool {
  332. return result.Year() == 2023 && result.Month() == 6 && result.Day() == 15 &&
  333. result.Hour() == 14 && result.Minute() == 30 && result.Second() == 0 &&
  334. result.Nanosecond() == 0
  335. },
  336. },
  337. {
  338. name: "Truncate from string date",
  339. precision: "day",
  340. value: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "2023-06-15 14:30:45"}},
  341. expectErr: false,
  342. expectedCheck: func(result time.Time) bool {
  343. // The result should be the start of day 2023-06-15 in local timezone
  344. expectedDay := time.Date(2023, 6, 15, 0, 0, 0, 0, result.Location())
  345. return result.Equal(expectedDay)
  346. },
  347. },
  348. {
  349. name: "Truncate null value",
  350. precision: "day",
  351. value: nil,
  352. expectErr: true,
  353. expectedCheck: nil,
  354. },
  355. {
  356. name: "Invalid precision",
  357. precision: "invalid",
  358. value: testTimestamp,
  359. expectErr: true,
  360. expectedCheck: nil,
  361. },
  362. }
  363. for _, tt := range tests {
  364. t.Run(tt.name, func(t *testing.T) {
  365. result, err := engine.DateTrunc(tt.precision, tt.value)
  366. if tt.expectErr {
  367. if err == nil {
  368. t.Errorf("Expected error but got none")
  369. }
  370. return
  371. }
  372. if err != nil {
  373. t.Errorf("Unexpected error: %v", err)
  374. return
  375. }
  376. if result == nil {
  377. t.Errorf("DateTrunc returned nil result")
  378. return
  379. }
  380. timestampVal, ok := result.Kind.(*schema_pb.Value_TimestampValue)
  381. if !ok {
  382. t.Errorf("DateTrunc should return timestamp value, got %T", result.Kind)
  383. return
  384. }
  385. resultTime := time.UnixMicro(timestampVal.TimestampValue.TimestampMicros)
  386. if !tt.expectedCheck(resultTime) {
  387. t.Errorf("DateTrunc result check failed for precision %s, got time: %v", tt.precision, resultTime)
  388. }
  389. })
  390. }
  391. }
  392. // TestDateTimeConstantsInSQL tests that datetime constants work in actual SQL queries
  393. // This test reproduces the original bug where CURRENT_TIME returned empty values
  394. func TestDateTimeConstantsInSQL(t *testing.T) {
  395. engine := NewTestSQLEngine()
  396. t.Run("CURRENT_TIME in SQL query", func(t *testing.T) {
  397. // This is the exact case that was failing
  398. result, err := engine.ExecuteSQL(context.Background(), "SELECT CURRENT_TIME FROM user_events LIMIT 1")
  399. if err != nil {
  400. t.Fatalf("SQL execution failed: %v", err)
  401. }
  402. if result.Error != nil {
  403. t.Fatalf("Query result has error: %v", result.Error)
  404. }
  405. // Verify we have the correct column and non-empty values
  406. if len(result.Columns) != 1 || result.Columns[0] != "current_time" {
  407. t.Errorf("Expected column 'current_time', got %v", result.Columns)
  408. }
  409. if len(result.Rows) == 0 {
  410. t.Fatal("Expected at least one row")
  411. }
  412. timeValue := result.Rows[0][0].ToString()
  413. if timeValue == "" {
  414. t.Error("CURRENT_TIME should not return empty value")
  415. }
  416. // Verify HH:MM:SS format
  417. if len(timeValue) == 8 && timeValue[2] == ':' && timeValue[5] == ':' {
  418. t.Logf("CURRENT_TIME returned valid time: %s", timeValue)
  419. } else {
  420. t.Errorf("CURRENT_TIME should return HH:MM:SS format, got: %s", timeValue)
  421. }
  422. })
  423. t.Run("CURRENT_DATE in SQL query", func(t *testing.T) {
  424. result, err := engine.ExecuteSQL(context.Background(), "SELECT CURRENT_DATE FROM user_events LIMIT 1")
  425. if err != nil {
  426. t.Fatalf("SQL execution failed: %v", err)
  427. }
  428. if result.Error != nil {
  429. t.Fatalf("Query result has error: %v", result.Error)
  430. }
  431. if len(result.Rows) == 0 {
  432. t.Fatal("Expected at least one row")
  433. }
  434. dateValue := result.Rows[0][0].ToString()
  435. if dateValue == "" {
  436. t.Error("CURRENT_DATE should not return empty value")
  437. }
  438. t.Logf("CURRENT_DATE returned: %s", dateValue)
  439. })
  440. }
  441. // TestFunctionArgumentCountHandling tests that the function evaluation correctly handles
  442. // both zero-argument and single-argument functions
  443. func TestFunctionArgumentCountHandling(t *testing.T) {
  444. engine := NewTestSQLEngine()
  445. t.Run("Zero-argument function should fail appropriately", func(t *testing.T) {
  446. funcExpr := &FuncExpr{
  447. Name: testStringValue(FuncCURRENT_TIME),
  448. Exprs: []SelectExpr{}, // Zero arguments - should fail since we removed zero-arg support
  449. }
  450. result, err := engine.evaluateStringFunction(funcExpr, HybridScanResult{})
  451. if err == nil {
  452. t.Error("Expected error for zero-argument function, but got none")
  453. }
  454. if result != nil {
  455. t.Error("Expected nil result for zero-argument function")
  456. }
  457. expectedError := "function CURRENT_TIME expects exactly 1 argument"
  458. if err.Error() != expectedError {
  459. t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
  460. }
  461. })
  462. t.Run("Single-argument function should still work", func(t *testing.T) {
  463. funcExpr := &FuncExpr{
  464. Name: testStringValue(FuncUPPER),
  465. Exprs: []SelectExpr{
  466. &AliasedExpr{
  467. Expr: &SQLVal{
  468. Type: StrVal,
  469. Val: []byte("test"),
  470. },
  471. },
  472. }, // Single argument - should work
  473. }
  474. // Create a mock result
  475. mockResult := HybridScanResult{}
  476. result, err := engine.evaluateStringFunction(funcExpr, mockResult)
  477. if err != nil {
  478. t.Errorf("Single-argument function failed: %v", err)
  479. }
  480. if result == nil {
  481. t.Errorf("Single-argument function returned nil")
  482. }
  483. })
  484. t.Run("Any zero-argument function should fail", func(t *testing.T) {
  485. funcExpr := &FuncExpr{
  486. Name: testStringValue("INVALID_FUNCTION"),
  487. Exprs: []SelectExpr{}, // Zero arguments - should fail
  488. }
  489. result, err := engine.evaluateStringFunction(funcExpr, HybridScanResult{})
  490. if err == nil {
  491. t.Error("Expected error for zero-argument function, got nil")
  492. }
  493. if result != nil {
  494. t.Errorf("Expected nil result for zero-argument function, got %v", result)
  495. }
  496. expectedError := "function INVALID_FUNCTION expects exactly 1 argument"
  497. if err.Error() != expectedError {
  498. t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
  499. }
  500. })
  501. t.Run("Wrong argument count for single-arg function should fail", func(t *testing.T) {
  502. funcExpr := &FuncExpr{
  503. Name: testStringValue(FuncUPPER),
  504. Exprs: []SelectExpr{
  505. &AliasedExpr{Expr: &SQLVal{Type: StrVal, Val: []byte("test1")}},
  506. &AliasedExpr{Expr: &SQLVal{Type: StrVal, Val: []byte("test2")}},
  507. }, // Two arguments - should fail for UPPER
  508. }
  509. result, err := engine.evaluateStringFunction(funcExpr, HybridScanResult{})
  510. if err == nil {
  511. t.Errorf("Expected error for wrong argument count, got nil")
  512. }
  513. if result != nil {
  514. t.Errorf("Expected nil result for wrong argument count, got %v", result)
  515. }
  516. expectedError := "function UPPER expects exactly 1 argument"
  517. if err.Error() != expectedError {
  518. t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
  519. }
  520. })
  521. }
  522. // Helper function to create a string value for testing
  523. func testStringValue(s string) StringGetter {
  524. return &testStringValueImpl{value: s}
  525. }
  526. type testStringValueImpl struct {
  527. value string
  528. }
  529. func (s *testStringValueImpl) String() string {
  530. return s.value
  531. }
  532. // TestExtractFunctionSQL tests the EXTRACT function through SQL execution
  533. func TestExtractFunctionSQL(t *testing.T) {
  534. engine := NewTestSQLEngine()
  535. testCases := []struct {
  536. name string
  537. sql string
  538. expectError bool
  539. checkValue func(t *testing.T, result *QueryResult)
  540. }{
  541. {
  542. name: "Extract YEAR from current_date",
  543. sql: "SELECT EXTRACT(YEAR FROM current_date) AS year_value FROM user_events LIMIT 1",
  544. expectError: false,
  545. checkValue: func(t *testing.T, result *QueryResult) {
  546. if len(result.Rows) == 0 {
  547. t.Fatal("Expected at least one row")
  548. }
  549. yearStr := result.Rows[0][0].ToString()
  550. currentYear := time.Now().Year()
  551. if yearStr != fmt.Sprintf("%d", currentYear) {
  552. t.Errorf("Expected current year %d, got %s", currentYear, yearStr)
  553. }
  554. },
  555. },
  556. {
  557. name: "Extract MONTH from current_date",
  558. sql: "SELECT EXTRACT('MONTH', current_date) AS month_value FROM user_events LIMIT 1",
  559. expectError: false,
  560. checkValue: func(t *testing.T, result *QueryResult) {
  561. if len(result.Rows) == 0 {
  562. t.Fatal("Expected at least one row")
  563. }
  564. monthStr := result.Rows[0][0].ToString()
  565. currentMonth := time.Now().Month()
  566. if monthStr != fmt.Sprintf("%d", int(currentMonth)) {
  567. t.Errorf("Expected current month %d, got %s", int(currentMonth), monthStr)
  568. }
  569. },
  570. },
  571. {
  572. name: "Extract DAY from current_date",
  573. sql: "SELECT EXTRACT('DAY', current_date) AS day_value FROM user_events LIMIT 1",
  574. expectError: false,
  575. checkValue: func(t *testing.T, result *QueryResult) {
  576. if len(result.Rows) == 0 {
  577. t.Fatal("Expected at least one row")
  578. }
  579. dayStr := result.Rows[0][0].ToString()
  580. currentDay := time.Now().Day()
  581. if dayStr != fmt.Sprintf("%d", currentDay) {
  582. t.Errorf("Expected current day %d, got %s", currentDay, dayStr)
  583. }
  584. },
  585. },
  586. {
  587. name: "Extract HOUR from current_timestamp",
  588. sql: "SELECT EXTRACT('HOUR', current_timestamp) AS hour_value FROM user_events LIMIT 1",
  589. expectError: false,
  590. checkValue: func(t *testing.T, result *QueryResult) {
  591. if len(result.Rows) == 0 {
  592. t.Fatal("Expected at least one row")
  593. }
  594. hourStr := result.Rows[0][0].ToString()
  595. // Just check it's a valid hour (0-23)
  596. hour, err := strconv.Atoi(hourStr)
  597. if err != nil {
  598. t.Errorf("Expected valid hour integer, got %s", hourStr)
  599. }
  600. if hour < 0 || hour > 23 {
  601. t.Errorf("Expected hour 0-23, got %d", hour)
  602. }
  603. },
  604. },
  605. {
  606. name: "Extract MINUTE from current_timestamp",
  607. sql: "SELECT EXTRACT('MINUTE', current_timestamp) AS minute_value FROM user_events LIMIT 1",
  608. expectError: false,
  609. checkValue: func(t *testing.T, result *QueryResult) {
  610. if len(result.Rows) == 0 {
  611. t.Fatal("Expected at least one row")
  612. }
  613. minuteStr := result.Rows[0][0].ToString()
  614. // Just check it's a valid minute (0-59)
  615. minute, err := strconv.Atoi(minuteStr)
  616. if err != nil {
  617. t.Errorf("Expected valid minute integer, got %s", minuteStr)
  618. }
  619. if minute < 0 || minute > 59 {
  620. t.Errorf("Expected minute 0-59, got %d", minute)
  621. }
  622. },
  623. },
  624. {
  625. name: "Extract QUARTER from current_date",
  626. sql: "SELECT EXTRACT('QUARTER', current_date) AS quarter_value FROM user_events LIMIT 1",
  627. expectError: false,
  628. checkValue: func(t *testing.T, result *QueryResult) {
  629. if len(result.Rows) == 0 {
  630. t.Fatal("Expected at least one row")
  631. }
  632. quarterStr := result.Rows[0][0].ToString()
  633. quarter, err := strconv.Atoi(quarterStr)
  634. if err != nil {
  635. t.Errorf("Expected valid quarter integer, got %s", quarterStr)
  636. }
  637. if quarter < 1 || quarter > 4 {
  638. t.Errorf("Expected quarter 1-4, got %d", quarter)
  639. }
  640. },
  641. },
  642. {
  643. name: "Multiple EXTRACT functions",
  644. sql: "SELECT EXTRACT(YEAR FROM current_date) AS year_val, EXTRACT(MONTH FROM current_date) AS month_val, EXTRACT(DAY FROM current_date) AS day_val FROM user_events LIMIT 1",
  645. expectError: false,
  646. checkValue: func(t *testing.T, result *QueryResult) {
  647. if len(result.Rows) == 0 {
  648. t.Fatal("Expected at least one row")
  649. }
  650. if len(result.Rows[0]) != 3 {
  651. t.Fatalf("Expected 3 columns, got %d", len(result.Rows[0]))
  652. }
  653. // Check year
  654. yearStr := result.Rows[0][0].ToString()
  655. currentYear := time.Now().Year()
  656. if yearStr != fmt.Sprintf("%d", currentYear) {
  657. t.Errorf("Expected current year %d, got %s", currentYear, yearStr)
  658. }
  659. // Check month
  660. monthStr := result.Rows[0][1].ToString()
  661. currentMonth := time.Now().Month()
  662. if monthStr != fmt.Sprintf("%d", int(currentMonth)) {
  663. t.Errorf("Expected current month %d, got %s", int(currentMonth), monthStr)
  664. }
  665. // Check day
  666. dayStr := result.Rows[0][2].ToString()
  667. currentDay := time.Now().Day()
  668. if dayStr != fmt.Sprintf("%d", currentDay) {
  669. t.Errorf("Expected current day %d, got %s", currentDay, dayStr)
  670. }
  671. },
  672. },
  673. {
  674. name: "EXTRACT with invalid date part",
  675. sql: "SELECT EXTRACT('INVALID_PART', current_date) FROM user_events LIMIT 1",
  676. expectError: true,
  677. checkValue: nil,
  678. },
  679. {
  680. name: "EXTRACT with wrong number of arguments",
  681. sql: "SELECT EXTRACT('YEAR') FROM user_events LIMIT 1",
  682. expectError: true,
  683. checkValue: nil,
  684. },
  685. {
  686. name: "EXTRACT with too many arguments",
  687. sql: "SELECT EXTRACT('YEAR', current_date, 'extra') FROM user_events LIMIT 1",
  688. expectError: true,
  689. checkValue: nil,
  690. },
  691. }
  692. for _, tc := range testCases {
  693. t.Run(tc.name, func(t *testing.T) {
  694. result, err := engine.ExecuteSQL(context.Background(), tc.sql)
  695. if tc.expectError {
  696. if err == nil && result.Error == nil {
  697. t.Errorf("Expected error but got none")
  698. }
  699. return
  700. }
  701. if err != nil {
  702. t.Errorf("Unexpected error: %v", err)
  703. return
  704. }
  705. if result.Error != nil {
  706. t.Errorf("Query result has error: %v", result.Error)
  707. return
  708. }
  709. if tc.checkValue != nil {
  710. tc.checkValue(t, result)
  711. }
  712. })
  713. }
  714. }
  715. // TestDateTruncFunctionSQL tests the DATE_TRUNC function through SQL execution
  716. func TestDateTruncFunctionSQL(t *testing.T) {
  717. engine := NewTestSQLEngine()
  718. testCases := []struct {
  719. name string
  720. sql string
  721. expectError bool
  722. checkValue func(t *testing.T, result *QueryResult)
  723. }{
  724. {
  725. name: "DATE_TRUNC to day",
  726. sql: "SELECT DATE_TRUNC('day', current_timestamp) AS truncated_day FROM user_events LIMIT 1",
  727. expectError: false,
  728. checkValue: func(t *testing.T, result *QueryResult) {
  729. if len(result.Rows) == 0 {
  730. t.Fatal("Expected at least one row")
  731. }
  732. // The result should be a timestamp value, just check it's not empty
  733. timestampStr := result.Rows[0][0].ToString()
  734. if timestampStr == "" {
  735. t.Error("Expected non-empty timestamp result")
  736. }
  737. },
  738. },
  739. {
  740. name: "DATE_TRUNC to hour",
  741. sql: "SELECT DATE_TRUNC('hour', current_timestamp) AS truncated_hour FROM user_events LIMIT 1",
  742. expectError: false,
  743. checkValue: func(t *testing.T, result *QueryResult) {
  744. if len(result.Rows) == 0 {
  745. t.Fatal("Expected at least one row")
  746. }
  747. timestampStr := result.Rows[0][0].ToString()
  748. if timestampStr == "" {
  749. t.Error("Expected non-empty timestamp result")
  750. }
  751. },
  752. },
  753. {
  754. name: "DATE_TRUNC to month",
  755. sql: "SELECT DATE_TRUNC('month', current_timestamp) AS truncated_month FROM user_events LIMIT 1",
  756. expectError: false,
  757. checkValue: func(t *testing.T, result *QueryResult) {
  758. if len(result.Rows) == 0 {
  759. t.Fatal("Expected at least one row")
  760. }
  761. timestampStr := result.Rows[0][0].ToString()
  762. if timestampStr == "" {
  763. t.Error("Expected non-empty timestamp result")
  764. }
  765. },
  766. },
  767. {
  768. name: "DATE_TRUNC with invalid precision",
  769. sql: "SELECT DATE_TRUNC('invalid', current_timestamp) FROM user_events LIMIT 1",
  770. expectError: true,
  771. checkValue: nil,
  772. },
  773. {
  774. name: "DATE_TRUNC with wrong number of arguments",
  775. sql: "SELECT DATE_TRUNC('day') FROM user_events LIMIT 1",
  776. expectError: true,
  777. checkValue: nil,
  778. },
  779. }
  780. for _, tc := range testCases {
  781. t.Run(tc.name, func(t *testing.T) {
  782. result, err := engine.ExecuteSQL(context.Background(), tc.sql)
  783. if tc.expectError {
  784. if err == nil && result.Error == nil {
  785. t.Errorf("Expected error but got none")
  786. }
  787. return
  788. }
  789. if err != nil {
  790. t.Errorf("Unexpected error: %v", err)
  791. return
  792. }
  793. if result.Error != nil {
  794. t.Errorf("Query result has error: %v", result.Error)
  795. return
  796. }
  797. if tc.checkValue != nil {
  798. tc.checkValue(t, result)
  799. }
  800. })
  801. }
  802. }