sortableNodeArray.compare called panic(err) when parseInt64 or ParseFloat failed, so a scalar tagged !!int or !!float whose value is not a number crashed yq with a Go stack trace. An explicit tag in the input document is enough to trigger it, no unusual expression needed. Log a warning and compare the raw strings instead, matching the timestamp branch a few lines above in the same function and the plain string fallback the function already ends with. Values that do parse still compare numerically.
227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
package yqlib
|
|
|
|
import "testing"
|
|
|
|
var sortByOperatorScenarios = []expressionScenario{
|
|
{
|
|
description: "Sort by string field",
|
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
|
expression: `sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: apple}, {a: banana}, {a: cat}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by string field",
|
|
skipDoc: true,
|
|
document: "[{a: banana},{a: apple}]",
|
|
expression: `sort_by(.a)[]`,
|
|
expected: []string{
|
|
"D0, P[0], (!!map)::{a: apple}\n",
|
|
"D0, P[1], (!!map)::{a: banana}\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by with null",
|
|
skipDoc: true,
|
|
document: "[{a: banana},null,{a: apple}]",
|
|
expression: `sort_by(.a)[]`,
|
|
expected: []string{
|
|
"D0, P[0], (!!null)::null\n",
|
|
"D0, P[1], (!!map)::{a: apple}\n",
|
|
"D0, P[2], (!!map)::{a: banana}\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by multiple fields",
|
|
document: "[{a: dog},{a: cat, b: banana},{a: cat, b: apple}]",
|
|
expression: `sort_by(.a, .b)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: cat, b: apple}, {a: cat, b: banana}, {a: dog}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by multiple fields",
|
|
skipDoc: true,
|
|
document: "[{a: dog, b: good},{a: cat, c: things},{a: cat, b: apple}]",
|
|
expression: `sort_by(.a, .b)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: cat, c: things}, {a: cat, b: apple}, {a: dog, b: good}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by multiple fields",
|
|
skipDoc: true,
|
|
document: "[{a: dog, b: 0.1},{a: cat, b: 0.01},{a: cat, b: 0.001}]",
|
|
expression: `sort_by(.a, .b)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: cat, b: 0.001}, {a: cat, b: 0.01}, {a: dog, b: 0.1}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort descending by string field",
|
|
subdescription: "Use sort with reverse to sort in descending order.",
|
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
|
expression: `sort_by(.a) | reverse`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: cat}, {a: banana}, {a: apple}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort array in place",
|
|
document: "cool: [{a: banana},{a: cat},{a: apple}]",
|
|
expression: `.cool |= sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!map)::cool: [{a: apple}, {a: banana}, {a: cat}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort array of objects by key",
|
|
subdescription: "Note that you can give sort_by complex expressions, not just paths",
|
|
document: "cool: [{b: banana},{a: banana},{c: banana}]",
|
|
expression: `.cool |= sort_by(keys | .[0])`,
|
|
expected: []string{
|
|
"D0, P[], (!!map)::cool: [{a: banana}, {b: banana}, {c: banana}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort a map",
|
|
subdescription: "Sorting a map, by default this will sort by the values",
|
|
document: "y: b\nz: a\nx: c\n",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!map)::z: a\ny: b\nx: c\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort a map by keys",
|
|
subdescription: "Use sort_by to sort a map using a custom function",
|
|
document: "Y: b\nz: a\nx: c\n",
|
|
expression: `sort_by(key | downcase)`,
|
|
expected: []string{
|
|
"D0, P[], (!!map)::x: c\nY: b\nz: a\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort is stable",
|
|
subdescription: "Note the order of the elements in unchanged when equal in sorting.",
|
|
document: "[{a: banana, b: 1}, {a: banana, b: 2}, {a: banana, b: 3}, {a: banana, b: 4}]",
|
|
expression: `sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: banana, b: 1}, {a: banana, b: 2}, {a: banana, b: 3}, {a: banana, b: 4}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by numeric field",
|
|
document: "[{a: 10},{a: 100},{a: 1}]",
|
|
expression: `sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: 1}, {a: 10}, {a: 100}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort by custom date field",
|
|
document: `[{a: "12-Jun-2011"},{a: "23-Dec-2010"},{a: "10-Aug-2011"}]`,
|
|
expression: `with_dtf("02-Jan-2006"; sort_by(.a))`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: \"23-Dec-2010\"}, {a: \"12-Jun-2011\"}, {a: \"10-Aug-2011\"}]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
document: "[{a: 1.1},{a: 1.001},{a: 1.01}]",
|
|
expression: `sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: 1.001}, {a: 1.01}, {a: 1.1}]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort, nulls come first",
|
|
document: "[8,3,null,6, true, false, cat]",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[null, false, true, 3, 6, 8, cat]\n",
|
|
},
|
|
},
|
|
{
|
|
description: "Sort, nulls come first",
|
|
skipDoc: true,
|
|
document: "[8,null]",
|
|
expression: `sort[]`,
|
|
expected: []string{
|
|
"D0, P[0], (!!null)::null\n",
|
|
"D0, P[1], (!!int)::8\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "false before true",
|
|
document: "[{a: false, b: 1}, {a: true, b: 2}, {a: false, b: 3}]",
|
|
expression: `sort_by(.a)`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[{a: false, b: 1}, {a: false, b: 3}, {a: true, b: 2}]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "head comment",
|
|
document: "# abc\n- def\n# ghi",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::# abc\n- def\n# ghi\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "Sort large integers (no int64 subtraction overflow)",
|
|
document: "[5000000000000000000, -5000000000000000000]",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[-5000000000000000000, 5000000000000000000]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "Sort values tagged as ints that are not ints",
|
|
document: "[!!int b, !!int a]",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[!!int a, !!int b]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "Sort values tagged as floats that are not numbers",
|
|
document: "[!!float b, !!float a]",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[!!float a, !!float b]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "Sort a number against a value tagged as a number that is not one",
|
|
document: "[!!int zz, !!float 1.5]",
|
|
expression: `sort`,
|
|
expected: []string{
|
|
"D0, P[], (!!seq)::[!!float 1.5, !!int zz]\n",
|
|
},
|
|
},
|
|
{
|
|
skipDoc: true,
|
|
description: "Sort a map by a value tagged as an int that is not one",
|
|
document: "{a: !!int notanumber, b: !!int 3}",
|
|
expression: `to_entries | sort_by(.value) | from_entries`,
|
|
expected: []string{
|
|
"D0, P[], (!!map)::b: !!int 3\na: !!int notanumber\n",
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestSortByOperatorScenarios(t *testing.T) {
|
|
for _, tt := range sortByOperatorScenarios {
|
|
testScenario(t, &tt)
|
|
}
|
|
documentOperatorScenarios(t, "sort", sortByOperatorScenarios)
|
|
}
|