Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could MapScan() get nil instead of zero value for null value? #1699

Open
fhwangyinan opened this issue May 22, 2023 · 1 comment
Open

Could MapScan() get nil instead of zero value for null value? #1699

fhwangyinan opened this issue May 22, 2023 · 1 comment

Comments

@fhwangyinan
Copy link

Hi,
I am new to using Gocql, and I'm currently trying to write a reusable function for selecting data. I want the function to return the query result dynamically. However, when using MapScan, null values are being converted to zero, which is causing confusion for me in distinguishing between zero and null. I have reviewed previous issues and documentation but haven't found a similar solution. Could you please provide some suggestions?

Please answer these questions before submitting your issue. Thanks!

What version of Cassandra are you using?

4.1.0

What version of Gocql are you using?

v1.3.2

What version of Go are you using?

v1.20

What did you do?


func (c *CassandraClient) Select(keyspace string, table string, columns []string, where []Filter) ([]map[string]interface{}, error) {
	query := fmt.Sprintf("SELECT %s FROM %s.%s", strings.Join(columns, ","), keyspace, table)
	var whereClauses []string
	var args []interface{}
	for _, v := range where {
		whereClauses = append(whereClauses, fmt.Sprintf("%s %s ?", v.Key, v.Operator))
		args = append(args, v.Value)
	}
	if len(whereClauses) > 0 {
		query += " WHERE " + strings.Join(whereClauses, " AND ")
	}
	query += " ALLOW FILTERING"
	logrus.Infof("CQL: %s , Args: %s", query, args)
	iter := c.session.Query(query, args...).Iter()
	results := make([]map[string]interface{}, 0)

	for {
		m := make(map[string]interface{}, len(columns))

		if !iter.MapScan(m) {
			break
		}
		// Handle NaN values
		for key, value := range m {
			if f, ok := value.(float64); ok && math.IsNaN(f) {
				m[key] = "NaN"
			}
		}
		results = append(results, m)
	}

	if err := iter.Close(); err != nil {
		return nil, err
	}
	return results, nil
}

What did you expect to see?

The null value in the m variable is distinguished from the zero value.

What did you see instead?

null is converted to zero values.

If you are having connectivity related issues please share the following additional information

Describe your Cassandra cluster

please provide the following information

  • output of nodetool status
  • output of SELECT peer, rpc_address FROM system.peers
  • rebuild your application with the gocql_debug tag and post the output
@martin-sucha
Copy link
Contributor

Hi!

gocql.Unmarshal returns zero values, that is probably because a gocql.Unmarshaler can handle null values itself.

We can't change the behavior of MapScan as that would be backward-incompatible change. Adding a MapScanNull or similar might be possible, but we should probably do something more generic, like allowing to provide a function that is applied on every level.

There is no easy way to populate map where null is mapped to nil interface with with the current API, but it is possible.
You can provide a custom a custom unmarshaler at the top level that then recursively handles UDTs, maps, lists etc. and calls gocql.Unmarshal(customUnmarshaler(child value)) recursively. That way you propagate your custom unmarshaler down the value tree and can unmarshal in any way you want.

By the way, for query building (and mapping values to structs if you have use case for that) you might find https://github.com/scylladb/gocqlx useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants