expression_test.go 24 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. // +build go1.7
  2. package expression
  3. import (
  4. "reflect"
  5. "strings"
  6. "testing"
  7. "github.com/aws/aws-sdk-go/aws"
  8. "github.com/aws/aws-sdk-go/service/dynamodb"
  9. )
  10. type exprErrorMode string
  11. const (
  12. noExpressionError exprErrorMode = ""
  13. // invalidEscChar error will occer if the escape char '$' is either followed
  14. // by an unsupported character or if the escape char is the last character
  15. invalidEscChar = "invalid escape"
  16. // outOfRange error will occur if there are more escaped chars than there are
  17. // actual values to be aliased.
  18. outOfRange = "out of range"
  19. // invalidBuilderOperand error will occur if an invalid operand is used
  20. // as input for Build()
  21. invalidExpressionBuildOperand = "BuildOperand error"
  22. // unsetBuilder error will occur if Build() is called on an unset Builder
  23. unsetBuilder = "unset parameter: Builder"
  24. // unsetConditionBuilder error will occur if an unset ConditionBuilder is
  25. // used in WithCondition()
  26. unsetConditionBuilder = "unset parameter: ConditionBuilder"
  27. )
  28. func TestBuild(t *testing.T) {
  29. cases := []struct {
  30. name string
  31. input Builder
  32. expected Expression
  33. err exprErrorMode
  34. }{
  35. {
  36. name: "condition",
  37. input: NewBuilder().WithCondition(Name("foo").Equal(Value(5))),
  38. expected: Expression{
  39. expressionMap: map[expressionType]string{
  40. condition: "#0 = :0",
  41. },
  42. namesMap: map[string]*string{
  43. "#0": aws.String("foo"),
  44. },
  45. valuesMap: map[string]*dynamodb.AttributeValue{
  46. ":0": {
  47. N: aws.String("5"),
  48. },
  49. },
  50. },
  51. },
  52. {
  53. name: "projection",
  54. input: NewBuilder().WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))),
  55. expected: Expression{
  56. expressionMap: map[expressionType]string{
  57. projection: "#0, #1, #2",
  58. },
  59. namesMap: map[string]*string{
  60. "#0": aws.String("foo"),
  61. "#1": aws.String("bar"),
  62. "#2": aws.String("baz"),
  63. },
  64. },
  65. },
  66. {
  67. name: "keyCondition",
  68. input: NewBuilder().WithKeyCondition(Key("foo").Equal(Value(5))),
  69. expected: Expression{
  70. expressionMap: map[expressionType]string{
  71. keyCondition: "#0 = :0",
  72. },
  73. namesMap: map[string]*string{
  74. "#0": aws.String("foo"),
  75. },
  76. valuesMap: map[string]*dynamodb.AttributeValue{
  77. ":0": {
  78. N: aws.String("5"),
  79. },
  80. },
  81. },
  82. },
  83. {
  84. name: "filter",
  85. input: NewBuilder().WithFilter(Name("foo").Equal(Value(5))),
  86. expected: Expression{
  87. expressionMap: map[expressionType]string{
  88. filter: "#0 = :0",
  89. },
  90. namesMap: map[string]*string{
  91. "#0": aws.String("foo"),
  92. },
  93. valuesMap: map[string]*dynamodb.AttributeValue{
  94. ":0": {
  95. N: aws.String("5"),
  96. },
  97. },
  98. },
  99. },
  100. {
  101. name: "update",
  102. input: NewBuilder().WithUpdate(Set(Name("foo"), (Value(5)))),
  103. expected: Expression{
  104. expressionMap: map[expressionType]string{
  105. update: "SET #0 = :0\n",
  106. },
  107. namesMap: map[string]*string{
  108. "#0": aws.String("foo"),
  109. },
  110. valuesMap: map[string]*dynamodb.AttributeValue{
  111. ":0": {
  112. N: aws.String("5"),
  113. },
  114. },
  115. },
  116. },
  117. {
  118. name: "compound",
  119. input: NewBuilder().
  120. WithCondition(Name("foo").Equal(Value(5))).
  121. WithFilter(Name("bar").LessThan(Value(6))).
  122. WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))).
  123. WithKeyCondition(Key("foo").Equal(Value(5))).
  124. WithUpdate(Set(Name("foo"), Value(5))),
  125. expected: Expression{
  126. expressionMap: map[expressionType]string{
  127. condition: "#0 = :0",
  128. filter: "#1 < :1",
  129. projection: "#0, #1, #2",
  130. keyCondition: "#0 = :2",
  131. update: "SET #0 = :3\n",
  132. },
  133. namesMap: map[string]*string{
  134. "#0": aws.String("foo"),
  135. "#1": aws.String("bar"),
  136. "#2": aws.String("baz"),
  137. },
  138. valuesMap: map[string]*dynamodb.AttributeValue{
  139. ":0": {
  140. N: aws.String("5"),
  141. },
  142. ":1": {
  143. N: aws.String("6"),
  144. },
  145. ":2": {
  146. N: aws.String("5"),
  147. },
  148. ":3": {
  149. N: aws.String("5"),
  150. },
  151. },
  152. },
  153. },
  154. {
  155. name: "invalid Builder",
  156. input: NewBuilder().WithCondition(Name("").Equal(Value(5))),
  157. err: invalidExpressionBuildOperand,
  158. },
  159. {
  160. name: "unset Builder",
  161. input: Builder{},
  162. err: unsetBuilder,
  163. },
  164. }
  165. for _, c := range cases {
  166. t.Run(c.name, func(t *testing.T) {
  167. actual, err := c.input.Build()
  168. if c.err != noExpressionError {
  169. if err == nil {
  170. t.Errorf("expect error %q, got no error", c.err)
  171. } else {
  172. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  173. t.Errorf("expect %q error message to be in %q", e, a)
  174. }
  175. }
  176. } else {
  177. if err != nil {
  178. t.Errorf("expect no error, got unexpected Error %q", err)
  179. }
  180. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  181. t.Errorf("expect %v, got %v", e, a)
  182. }
  183. }
  184. })
  185. }
  186. }
  187. func TestCondition(t *testing.T) {
  188. cases := []struct {
  189. name string
  190. input Builder
  191. expected *string
  192. err exprErrorMode
  193. }{
  194. {
  195. name: "condition",
  196. input: Builder{
  197. expressionMap: map[expressionType]treeBuilder{
  198. condition: Name("foo").Equal(Value(5)),
  199. },
  200. },
  201. expected: aws.String("#0 = :0"),
  202. },
  203. {
  204. name: "unset builder",
  205. input: Builder{},
  206. err: unsetBuilder,
  207. },
  208. }
  209. for _, c := range cases {
  210. t.Run(c.name, func(t *testing.T) {
  211. expr, err := c.input.Build()
  212. if c.err != noExpressionError {
  213. if err == nil {
  214. t.Errorf("expect error %q, got no error", c.err)
  215. } else {
  216. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  217. t.Errorf("expect %q error message to be in %q", e, a)
  218. }
  219. }
  220. } else {
  221. if err != nil {
  222. t.Errorf("expect no error, got unexpected Error %q", err)
  223. }
  224. }
  225. actual := expr.Condition()
  226. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  227. t.Errorf("expect %v, got %v", e, a)
  228. }
  229. })
  230. }
  231. }
  232. func TestFilter(t *testing.T) {
  233. cases := []struct {
  234. name string
  235. input Builder
  236. expected *string
  237. err exprErrorMode
  238. }{
  239. {
  240. name: "filter",
  241. input: Builder{
  242. expressionMap: map[expressionType]treeBuilder{
  243. filter: Name("foo").Equal(Value(5)),
  244. },
  245. },
  246. expected: aws.String("#0 = :0"),
  247. },
  248. {
  249. name: "unset builder",
  250. input: Builder{},
  251. err: unsetBuilder,
  252. },
  253. }
  254. for _, c := range cases {
  255. t.Run(c.name, func(t *testing.T) {
  256. expr, err := c.input.Build()
  257. if c.err != noExpressionError {
  258. if err == nil {
  259. t.Errorf("expect error %q, got no error", c.err)
  260. } else {
  261. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  262. t.Errorf("expect %q error message to be in %q", e, a)
  263. }
  264. }
  265. } else {
  266. if err != nil {
  267. t.Errorf("expect no error, got unexpected Error %q", err)
  268. }
  269. }
  270. actual := expr.Filter()
  271. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  272. t.Errorf("expect %v, got %v", e, a)
  273. }
  274. })
  275. }
  276. }
  277. func TestProjection(t *testing.T) {
  278. cases := []struct {
  279. name string
  280. input Builder
  281. expected *string
  282. err exprErrorMode
  283. }{
  284. {
  285. name: "projection",
  286. input: Builder{
  287. expressionMap: map[expressionType]treeBuilder{
  288. projection: NamesList(Name("foo"), Name("bar"), Name("baz")),
  289. },
  290. },
  291. expected: aws.String("#0, #1, #2"),
  292. },
  293. {
  294. name: "unset builder",
  295. input: Builder{},
  296. err: unsetBuilder,
  297. },
  298. }
  299. for _, c := range cases {
  300. t.Run(c.name, func(t *testing.T) {
  301. expr, err := c.input.Build()
  302. if c.err != noExpressionError {
  303. if err == nil {
  304. t.Errorf("expect error %q, got no error", c.err)
  305. } else {
  306. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  307. t.Errorf("expect %q error message to be in %q", e, a)
  308. }
  309. }
  310. } else {
  311. if err != nil {
  312. t.Errorf("expect no error, got unexpected Error %q", err)
  313. }
  314. }
  315. actual := expr.Projection()
  316. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  317. t.Errorf("expect %v, got %v", e, a)
  318. }
  319. })
  320. }
  321. }
  322. func TestKeyCondition(t *testing.T) {
  323. cases := []struct {
  324. name string
  325. input Builder
  326. expected *string
  327. err exprErrorMode
  328. }{
  329. {
  330. name: "keyCondition",
  331. input: Builder{
  332. expressionMap: map[expressionType]treeBuilder{
  333. keyCondition: KeyConditionBuilder{
  334. operandList: []OperandBuilder{
  335. KeyBuilder{
  336. key: "foo",
  337. },
  338. ValueBuilder{
  339. value: 5,
  340. },
  341. },
  342. mode: equalKeyCond,
  343. },
  344. },
  345. },
  346. expected: aws.String("#0 = :0"),
  347. },
  348. {
  349. name: "empty builder",
  350. input: Builder{},
  351. err: unsetBuilder,
  352. },
  353. }
  354. for _, c := range cases {
  355. t.Run(c.name, func(t *testing.T) {
  356. expr, err := c.input.Build()
  357. if c.err != noExpressionError {
  358. if err == nil {
  359. t.Errorf("expect error %q, got no error", c.err)
  360. } else {
  361. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  362. t.Errorf("expect %q error message to be in %q", e, a)
  363. }
  364. }
  365. } else {
  366. if err != nil {
  367. t.Errorf("expect no error, got unexpected Error %q", err)
  368. }
  369. }
  370. actual := expr.KeyCondition()
  371. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  372. t.Errorf("expect %v, got %v", e, a)
  373. }
  374. })
  375. }
  376. }
  377. func TestUpdate(t *testing.T) {
  378. cases := []struct {
  379. name string
  380. input Builder
  381. expected *string
  382. err exprErrorMode
  383. }{
  384. {
  385. name: "update",
  386. input: Builder{
  387. expressionMap: map[expressionType]treeBuilder{
  388. update: UpdateBuilder{
  389. operationList: map[operationMode][]operationBuilder{
  390. setOperation: {
  391. {
  392. name: NameBuilder{
  393. name: "foo",
  394. },
  395. value: ValueBuilder{
  396. value: 5,
  397. },
  398. mode: setOperation,
  399. },
  400. },
  401. },
  402. },
  403. },
  404. },
  405. expected: aws.String("SET #0 = :0\n"),
  406. },
  407. {
  408. name: "multiple sets",
  409. input: Builder{
  410. expressionMap: map[expressionType]treeBuilder{
  411. update: UpdateBuilder{
  412. operationList: map[operationMode][]operationBuilder{
  413. setOperation: {
  414. {
  415. name: NameBuilder{
  416. name: "foo",
  417. },
  418. value: ValueBuilder{
  419. value: 5,
  420. },
  421. mode: setOperation,
  422. },
  423. {
  424. name: NameBuilder{
  425. name: "bar",
  426. },
  427. value: ValueBuilder{
  428. value: 6,
  429. },
  430. mode: setOperation,
  431. },
  432. {
  433. name: NameBuilder{
  434. name: "baz",
  435. },
  436. value: ValueBuilder{
  437. value: 7,
  438. },
  439. mode: setOperation,
  440. },
  441. },
  442. },
  443. },
  444. },
  445. },
  446. expected: aws.String("SET #0 = :0, #1 = :1, #2 = :2\n"),
  447. },
  448. {
  449. name: "unset builder",
  450. input: Builder{},
  451. err: unsetBuilder,
  452. },
  453. }
  454. for _, c := range cases {
  455. t.Run(c.name, func(t *testing.T) {
  456. expr, err := c.input.Build()
  457. if c.err != noExpressionError {
  458. if err == nil {
  459. t.Errorf("expect error %q, got no error", c.err)
  460. } else {
  461. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  462. t.Errorf("expect %q error message to be in %q", e, a)
  463. }
  464. }
  465. } else {
  466. if err != nil {
  467. t.Errorf("expect no error, got unexpected Error %q", err)
  468. }
  469. }
  470. actual := expr.Update()
  471. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  472. t.Errorf("expect %v, got %v", e, a)
  473. }
  474. })
  475. }
  476. }
  477. func TestNames(t *testing.T) {
  478. cases := []struct {
  479. name string
  480. input Builder
  481. expected map[string]*string
  482. err exprErrorMode
  483. }{
  484. {
  485. name: "projection",
  486. input: Builder{
  487. expressionMap: map[expressionType]treeBuilder{
  488. projection: NamesList(Name("foo"), Name("bar"), Name("baz")),
  489. },
  490. },
  491. expected: map[string]*string{
  492. "#0": aws.String("foo"),
  493. "#1": aws.String("bar"),
  494. "#2": aws.String("baz"),
  495. },
  496. },
  497. {
  498. name: "aggregate",
  499. input: Builder{
  500. expressionMap: map[expressionType]treeBuilder{
  501. condition: ConditionBuilder{
  502. operandList: []OperandBuilder{
  503. NameBuilder{
  504. name: "foo",
  505. },
  506. ValueBuilder{
  507. value: 5,
  508. },
  509. },
  510. mode: equalCond,
  511. },
  512. filter: ConditionBuilder{
  513. operandList: []OperandBuilder{
  514. NameBuilder{
  515. name: "bar",
  516. },
  517. ValueBuilder{
  518. value: 6,
  519. },
  520. },
  521. mode: lessThanCond,
  522. },
  523. projection: ProjectionBuilder{
  524. names: []NameBuilder{
  525. {
  526. name: "foo",
  527. },
  528. {
  529. name: "bar",
  530. },
  531. {
  532. name: "baz",
  533. },
  534. },
  535. },
  536. },
  537. },
  538. expected: map[string]*string{
  539. "#0": aws.String("foo"),
  540. "#1": aws.String("bar"),
  541. "#2": aws.String("baz"),
  542. },
  543. },
  544. {
  545. name: "unset",
  546. input: Builder{},
  547. err: unsetBuilder,
  548. },
  549. {
  550. name: "unset ConditionBuilder",
  551. input: NewBuilder().WithCondition(ConditionBuilder{}),
  552. err: unsetConditionBuilder,
  553. },
  554. }
  555. for _, c := range cases {
  556. t.Run(c.name, func(t *testing.T) {
  557. expr, err := c.input.Build()
  558. if c.err != noExpressionError {
  559. if err == nil {
  560. t.Errorf("expect error %q, got no error", c.err)
  561. } else {
  562. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  563. t.Errorf("expect %q error message to be in %q", e, a)
  564. }
  565. }
  566. } else {
  567. if err != nil {
  568. t.Errorf("expect no error, got unexpected Error %q", err)
  569. }
  570. }
  571. actual := expr.Names()
  572. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  573. t.Errorf("expect %v, got %v", e, a)
  574. }
  575. })
  576. }
  577. }
  578. func TestValues(t *testing.T) {
  579. cases := []struct {
  580. name string
  581. input Builder
  582. expected map[string]*dynamodb.AttributeValue
  583. err exprErrorMode
  584. }{
  585. {
  586. name: "condition",
  587. input: Builder{
  588. expressionMap: map[expressionType]treeBuilder{
  589. condition: Name("foo").Equal(Value(5)),
  590. },
  591. },
  592. expected: map[string]*dynamodb.AttributeValue{
  593. ":0": {
  594. N: aws.String("5"),
  595. },
  596. },
  597. },
  598. {
  599. name: "aggregate",
  600. input: Builder{
  601. expressionMap: map[expressionType]treeBuilder{
  602. condition: ConditionBuilder{
  603. operandList: []OperandBuilder{
  604. NameBuilder{
  605. name: "foo",
  606. },
  607. ValueBuilder{
  608. value: 5,
  609. },
  610. },
  611. mode: equalCond,
  612. },
  613. filter: ConditionBuilder{
  614. operandList: []OperandBuilder{
  615. NameBuilder{
  616. name: "bar",
  617. },
  618. ValueBuilder{
  619. value: 6,
  620. },
  621. },
  622. mode: lessThanCond,
  623. },
  624. projection: ProjectionBuilder{
  625. names: []NameBuilder{
  626. {
  627. name: "foo",
  628. },
  629. {
  630. name: "bar",
  631. },
  632. {
  633. name: "baz",
  634. },
  635. },
  636. },
  637. },
  638. },
  639. expected: map[string]*dynamodb.AttributeValue{
  640. ":0": {
  641. N: aws.String("5"),
  642. },
  643. ":1": {
  644. N: aws.String("6"),
  645. },
  646. },
  647. },
  648. {
  649. name: "unset",
  650. input: Builder{},
  651. err: unsetBuilder,
  652. },
  653. }
  654. for _, c := range cases {
  655. t.Run(c.name, func(t *testing.T) {
  656. expr, err := c.input.Build()
  657. if c.err != noExpressionError {
  658. if err == nil {
  659. t.Errorf("expect error %q, got no error", c.err)
  660. } else {
  661. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  662. t.Errorf("expect %q error message to be in %q", e, a)
  663. }
  664. }
  665. } else {
  666. if err != nil {
  667. t.Errorf("expect no error, got unexpected Error %q", err)
  668. }
  669. }
  670. actual := expr.Values()
  671. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  672. t.Errorf("expect %v, got %v", e, a)
  673. }
  674. })
  675. }
  676. }
  677. func TestBuildChildTrees(t *testing.T) {
  678. cases := []struct {
  679. name string
  680. input Builder
  681. expectedaliasList aliasList
  682. expectedStringMap map[expressionType]string
  683. err exprErrorMode
  684. }{
  685. {
  686. name: "aggregate",
  687. input: Builder{
  688. expressionMap: map[expressionType]treeBuilder{
  689. condition: ConditionBuilder{
  690. operandList: []OperandBuilder{
  691. NameBuilder{
  692. name: "foo",
  693. },
  694. ValueBuilder{
  695. value: 5,
  696. },
  697. },
  698. mode: equalCond,
  699. },
  700. filter: ConditionBuilder{
  701. operandList: []OperandBuilder{
  702. NameBuilder{
  703. name: "bar",
  704. },
  705. ValueBuilder{
  706. value: 6,
  707. },
  708. },
  709. mode: lessThanCond,
  710. },
  711. projection: ProjectionBuilder{
  712. names: []NameBuilder{
  713. {
  714. name: "foo",
  715. },
  716. {
  717. name: "bar",
  718. },
  719. {
  720. name: "baz",
  721. },
  722. },
  723. },
  724. },
  725. },
  726. expectedaliasList: aliasList{
  727. namesList: []string{"foo", "bar", "baz"},
  728. valuesList: []dynamodb.AttributeValue{
  729. {
  730. N: aws.String("5"),
  731. },
  732. {
  733. N: aws.String("6"),
  734. },
  735. },
  736. },
  737. expectedStringMap: map[expressionType]string{
  738. condition: "#0 = :0",
  739. filter: "#1 < :1",
  740. projection: "#0, #1, #2",
  741. },
  742. },
  743. {
  744. name: "unset",
  745. input: Builder{},
  746. expectedaliasList: aliasList{},
  747. expectedStringMap: map[expressionType]string{},
  748. },
  749. }
  750. for _, c := range cases {
  751. t.Run(c.name, func(t *testing.T) {
  752. actualAL, actualSM, err := c.input.buildChildTrees()
  753. if c.err != noExpressionError {
  754. if err == nil {
  755. t.Errorf("expect error %q, got no error", c.err)
  756. } else {
  757. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  758. t.Errorf("expect %q error message to be in %q", e, a)
  759. }
  760. }
  761. } else {
  762. if err != nil {
  763. t.Errorf("expect no error, got unexpected Error %q", err)
  764. }
  765. }
  766. if e, a := c.expectedaliasList, actualAL; !reflect.DeepEqual(a, e) {
  767. t.Errorf("expect %v, got %v", e, a)
  768. }
  769. if e, a := c.expectedStringMap, actualSM; !reflect.DeepEqual(a, e) {
  770. t.Errorf("expect %v, got %v", e, a)
  771. }
  772. })
  773. }
  774. }
  775. func TestBuildExpressionString(t *testing.T) {
  776. cases := []struct {
  777. name string
  778. input exprNode
  779. expectedNames map[string]*string
  780. expectedValues map[string]*dynamodb.AttributeValue
  781. expectedExpression string
  782. err exprErrorMode
  783. }{
  784. {
  785. name: "basic name",
  786. input: exprNode{
  787. names: []string{"foo"},
  788. fmtExpr: "$n",
  789. },
  790. expectedValues: map[string]*dynamodb.AttributeValue{},
  791. expectedNames: map[string]*string{
  792. "#0": aws.String("foo"),
  793. },
  794. expectedExpression: "#0",
  795. },
  796. {
  797. name: "basic value",
  798. input: exprNode{
  799. values: []dynamodb.AttributeValue{
  800. {
  801. N: aws.String("5"),
  802. },
  803. },
  804. fmtExpr: "$v",
  805. },
  806. expectedNames: map[string]*string{},
  807. expectedValues: map[string]*dynamodb.AttributeValue{
  808. ":0": {
  809. N: aws.String("5"),
  810. },
  811. },
  812. expectedExpression: ":0",
  813. },
  814. {
  815. name: "nested path",
  816. input: exprNode{
  817. names: []string{"foo", "bar"},
  818. fmtExpr: "$n.$n",
  819. },
  820. expectedValues: map[string]*dynamodb.AttributeValue{},
  821. expectedNames: map[string]*string{
  822. "#0": aws.String("foo"),
  823. "#1": aws.String("bar"),
  824. },
  825. expectedExpression: "#0.#1",
  826. },
  827. {
  828. name: "nested path with index",
  829. input: exprNode{
  830. names: []string{"foo", "bar", "baz"},
  831. fmtExpr: "$n.$n[0].$n",
  832. },
  833. expectedValues: map[string]*dynamodb.AttributeValue{},
  834. expectedNames: map[string]*string{
  835. "#0": aws.String("foo"),
  836. "#1": aws.String("bar"),
  837. "#2": aws.String("baz"),
  838. },
  839. expectedExpression: "#0.#1[0].#2",
  840. },
  841. {
  842. name: "basic size",
  843. input: exprNode{
  844. names: []string{"foo"},
  845. fmtExpr: "size ($n)",
  846. },
  847. expectedValues: map[string]*dynamodb.AttributeValue{},
  848. expectedNames: map[string]*string{
  849. "#0": aws.String("foo"),
  850. },
  851. expectedExpression: "size (#0)",
  852. },
  853. {
  854. name: "duplicate path name",
  855. input: exprNode{
  856. names: []string{"foo", "foo"},
  857. fmtExpr: "$n.$n",
  858. },
  859. expectedValues: map[string]*dynamodb.AttributeValue{},
  860. expectedNames: map[string]*string{
  861. "#0": aws.String("foo"),
  862. },
  863. expectedExpression: "#0.#0",
  864. },
  865. {
  866. name: "equal expression",
  867. input: exprNode{
  868. children: []exprNode{
  869. {
  870. names: []string{"foo"},
  871. fmtExpr: "$n",
  872. },
  873. {
  874. values: []dynamodb.AttributeValue{
  875. {
  876. N: aws.String("5"),
  877. },
  878. },
  879. fmtExpr: "$v",
  880. },
  881. },
  882. fmtExpr: "$c = $c",
  883. },
  884. expectedNames: map[string]*string{
  885. "#0": aws.String("foo"),
  886. },
  887. expectedValues: map[string]*dynamodb.AttributeValue{
  888. ":0": {
  889. N: aws.String("5"),
  890. },
  891. },
  892. expectedExpression: "#0 = :0",
  893. },
  894. {
  895. name: "missing char after $",
  896. input: exprNode{
  897. names: []string{"foo", "foo"},
  898. fmtExpr: "$n.$",
  899. },
  900. err: invalidEscChar,
  901. },
  902. {
  903. name: "names out of range",
  904. input: exprNode{
  905. names: []string{"foo"},
  906. fmtExpr: "$n.$n",
  907. },
  908. err: outOfRange,
  909. },
  910. {
  911. name: "values out of range",
  912. input: exprNode{
  913. fmtExpr: "$v",
  914. },
  915. err: outOfRange,
  916. },
  917. {
  918. name: "children out of range",
  919. input: exprNode{
  920. fmtExpr: "$c",
  921. },
  922. err: outOfRange,
  923. },
  924. {
  925. name: "invalid escape char",
  926. input: exprNode{
  927. fmtExpr: "$!",
  928. },
  929. err: invalidEscChar,
  930. },
  931. {
  932. name: "unset exprNode",
  933. input: exprNode{},
  934. expectedExpression: "",
  935. },
  936. }
  937. for _, c := range cases {
  938. t.Run(c.name, func(t *testing.T) {
  939. expr, err := c.input.buildExpressionString(&aliasList{})
  940. if c.err != noExpressionError {
  941. if err == nil {
  942. t.Errorf("expect error %q, got no error", c.err)
  943. } else {
  944. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  945. t.Errorf("expect %q error message to be in %q", e, a)
  946. }
  947. }
  948. } else {
  949. if err != nil {
  950. t.Errorf("expect no error, got unexpected Error %q", err)
  951. }
  952. if e, a := c.expectedExpression, expr; !reflect.DeepEqual(a, e) {
  953. t.Errorf("expect %v, got %v", e, a)
  954. }
  955. }
  956. })
  957. }
  958. }
  959. func TestReturnExpression(t *testing.T) {
  960. cases := []struct {
  961. name string
  962. input Expression
  963. expected *string
  964. }{
  965. {
  966. name: "projection exists",
  967. input: Expression{
  968. expressionMap: map[expressionType]string{
  969. projection: "#0, #1, #2",
  970. },
  971. },
  972. expected: aws.String("#0, #1, #2"),
  973. },
  974. {
  975. name: "projection not exists",
  976. input: Expression{
  977. expressionMap: map[expressionType]string{},
  978. },
  979. expected: nil,
  980. },
  981. }
  982. for _, c := range cases {
  983. t.Run(c.name, func(t *testing.T) {
  984. actual := c.input.returnExpression(projection)
  985. if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  986. t.Errorf("expect %v, got %v", e, a)
  987. }
  988. })
  989. }
  990. }
  991. func TestAliasValue(t *testing.T) {
  992. cases := []struct {
  993. name string
  994. input *aliasList
  995. expected string
  996. err exprErrorMode
  997. }{
  998. {
  999. name: "first item",
  1000. input: &aliasList{},
  1001. expected: ":0",
  1002. },
  1003. {
  1004. name: "fifth item",
  1005. input: &aliasList{
  1006. valuesList: []dynamodb.AttributeValue{
  1007. {},
  1008. {},
  1009. {},
  1010. {},
  1011. },
  1012. },
  1013. expected: ":4",
  1014. },
  1015. }
  1016. for _, c := range cases {
  1017. t.Run(c.name, func(t *testing.T) {
  1018. str, err := c.input.aliasValue(dynamodb.AttributeValue{})
  1019. if c.err != noExpressionError {
  1020. if err == nil {
  1021. t.Errorf("expect error %q, got no error", c.err)
  1022. } else {
  1023. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  1024. t.Errorf("expect %q error message to be in %q", e, a)
  1025. }
  1026. }
  1027. } else {
  1028. if err != nil {
  1029. t.Errorf("expect no error, got unexpected Error %q", err)
  1030. }
  1031. if e, a := c.expected, str; e != a {
  1032. t.Errorf("expect %v, got %v", e, a)
  1033. }
  1034. }
  1035. })
  1036. }
  1037. }
  1038. func TestAliasPath(t *testing.T) {
  1039. cases := []struct {
  1040. name string
  1041. inputList *aliasList
  1042. inputName string
  1043. expected string
  1044. err exprErrorMode
  1045. }{
  1046. {
  1047. name: "new unique item",
  1048. inputList: &aliasList{},
  1049. inputName: "foo",
  1050. expected: "#0",
  1051. },
  1052. {
  1053. name: "duplicate item",
  1054. inputList: &aliasList{
  1055. namesList: []string{
  1056. "foo",
  1057. "bar",
  1058. },
  1059. },
  1060. inputName: "foo",
  1061. expected: "#0",
  1062. },
  1063. }
  1064. for _, c := range cases {
  1065. t.Run(c.name, func(t *testing.T) {
  1066. str, err := c.inputList.aliasPath(c.inputName)
  1067. if c.err != noExpressionError {
  1068. if err == nil {
  1069. t.Errorf("expect error %q, got no error", c.err)
  1070. } else {
  1071. if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  1072. t.Errorf("expect %q error message to be in %q", e, a)
  1073. }
  1074. }
  1075. } else {
  1076. if err != nil {
  1077. t.Errorf("expect no error, got unexpected Error %q", err)
  1078. }
  1079. if e, a := c.expected, str; e != a {
  1080. t.Errorf("expect %v, got %v", e, a)
  1081. }
  1082. }
  1083. })
  1084. }
  1085. }