Example #1
0
// FromObject is used to compute the index key when inserting or updating an
// object.
func (*PreparedQueryIndex) FromObject(obj interface{}) (bool, []byte, error) {
	wrapped, ok := obj.(*queryWrapper)
	if !ok {
		return false, nil, fmt.Errorf("invalid object given to index as prepared query")
	}

	query := toPreparedQuery(wrapped)
	if !prepared_query.IsTemplate(query) {
		return false, nil, nil
	}

	// Always prepend a null so that we can represent even an empty name.
	out := "\x00" + strings.ToLower(query.Name)
	return true, []byte(out), nil
}
Example #2
0
// PrepparedQuery is used when restoring from a snapshot. For general inserts,
// use PreparedQuerySet.
func (s *StateRestore) PreparedQuery(query *structs.PreparedQuery) error {
	// If this is a template, compile it, otherwise leave the compiled
	// template field nil.
	var ct *prepared_query.CompiledTemplate
	if prepared_query.IsTemplate(query) {
		var err error
		ct, err = prepared_query.Compile(query)
		if err != nil {
			return fmt.Errorf("failed compiling template: %s", err)
		}
	}

	// Insert the wrapped query.
	if err := s.tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil {
		return fmt.Errorf("failed restoring prepared query: %s", err)
	}
	if err := indexUpdateMaxTxn(s.tx, query.ModifyIndex, "prepared-queries"); err != nil {
		return fmt.Errorf("failed updating index: %s", err)
	}

	s.watches.Arm("prepared-queries")
	return nil
}
Example #3
0
// preparedQuerySetTxn is the inner method used to insert a prepared query with
// the proper indexes into the state store.
func (s *StateStore) preparedQuerySetTxn(tx *memdb.Txn, idx uint64, query *structs.PreparedQuery) error {
	// Check that the ID is set.
	if query.ID == "" {
		return ErrMissingQueryID
	}

	// Check for an existing query.
	wrapped, err := tx.First("prepared-queries", "id", query.ID)
	if err != nil {
		return fmt.Errorf("failed prepared query lookup: %s", err)
	}
	existing := toPreparedQuery(wrapped)

	// Set the indexes.
	if existing != nil {
		query.CreateIndex = existing.CreateIndex
		query.ModifyIndex = idx
	} else {
		query.CreateIndex = idx
		query.ModifyIndex = idx
	}

	// Verify that the query name doesn't already exist, or that we are
	// updating the same instance that has this name. If this is a template
	// and the name is empty then we make sure there's not an empty template
	// already registered.
	if query.Name != "" {
		wrapped, err := tx.First("prepared-queries", "name", query.Name)
		if err != nil {
			return fmt.Errorf("failed prepared query lookup: %s", err)
		}
		other := toPreparedQuery(wrapped)
		if other != nil && (existing == nil || existing.ID != other.ID) {
			return fmt.Errorf("name '%s' aliases an existing query name", query.Name)
		}
	} else if prepared_query.IsTemplate(query) {
		wrapped, err := tx.First("prepared-queries", "template", query.Name)
		if err != nil {
			return fmt.Errorf("failed prepared query lookup: %s", err)
		}
		other := toPreparedQuery(wrapped)
		if other != nil && (existing == nil || existing.ID != other.ID) {
			return fmt.Errorf("a query template with an empty name already exists")
		}
	}

	// Verify that the name doesn't alias any existing ID. We allow queries
	// to be looked up by ID *or* name so we don't want anyone to try to
	// register a query with a name equal to some other query's ID in an
	// attempt to hijack it. We also look up by ID *then* name in order to
	// prevent this, but it seems prudent to prevent these types of rogue
	// queries from ever making it into the state store. Note that we have
	// to see if the name looks like a UUID before checking since the UUID
	// index will complain if we look up something that's not formatted
	// like one.
	if isUUID(query.Name) {
		wrapped, err := tx.First("prepared-queries", "id", query.Name)
		if err != nil {
			return fmt.Errorf("failed prepared query lookup: %s", err)
		}
		if wrapped != nil {
			return fmt.Errorf("name '%s' aliases an existing query ID", query.Name)
		}
	}

	// Verify that the session exists.
	if query.Session != "" {
		sess, err := tx.First("sessions", "id", query.Session)
		if err != nil {
			return fmt.Errorf("failed session lookup: %s", err)
		}
		if sess == nil {
			return fmt.Errorf("invalid session %#v", query.Session)
		}
	}

	// We do not verify the service here, nor the token, if any. These are
	// checked at execute time and not doing integrity checking on them
	// helps avoid bootstrapping chicken and egg problems.

	// If this is a template, compile it, otherwise leave the compiled
	// template field nil.
	var ct *prepared_query.CompiledTemplate
	if prepared_query.IsTemplate(query) {
		var err error
		ct, err = prepared_query.Compile(query)
		if err != nil {
			return fmt.Errorf("failed compiling template: %s", err)
		}
	}

	// Insert the wrapped query.
	if err := tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil {
		return fmt.Errorf("failed inserting prepared query: %s", err)
	}
	if err := tx.Insert("index", &IndexEntry{"prepared-queries", idx}); err != nil {
		return fmt.Errorf("failed updating index: %s", err)
	}

	tx.Defer(func() { s.tableWatches["prepared-queries"].Notify() })
	return nil
}
Example #4
0
// PreparedQueryResolve returns the given prepared query by looking up an ID or
// Name. If the query was looked up by name and it's a template, then the
// template will be rendered before it is returned.
func (s *StateStore) PreparedQueryResolve(queryIDOrName string) (uint64, *structs.PreparedQuery, error) {
	tx := s.db.Txn(false)
	defer tx.Abort()

	// Get the table index.
	idx := maxIndexTxn(tx, s.getWatchTables("PreparedQueryResolve")...)

	// Explicitly ban an empty query. This will never match an ID and the
	// schema is set up so it will never match a query with an empty name,
	// but we check it here to be explicit about it (we'd never want to
	// return the results from the first query w/o a name).
	if queryIDOrName == "" {
		return 0, nil, ErrMissingQueryID
	}

	// Try first by ID if it looks like they gave us an ID. We check the
	// format before trying this because the UUID index will complain if
	// we look up something that's not formatted like one.
	if isUUID(queryIDOrName) {
		wrapped, err := tx.First("prepared-queries", "id", queryIDOrName)
		if err != nil {
			return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
		}
		if wrapped != nil {
			query := toPreparedQuery(wrapped)
			if prepared_query.IsTemplate(query) {
				return idx, nil, fmt.Errorf("prepared query templates can only be resolved up by name, not by ID")
			}
			return idx, query, nil
		}
	}

	// prep will check to see if the query is a template and render it
	// first, otherwise it will just return a regular query.
	prep := func(wrapped interface{}) (uint64, *structs.PreparedQuery, error) {
		wrapper := wrapped.(*queryWrapper)
		if prepared_query.IsTemplate(wrapper.PreparedQuery) {
			render, err := wrapper.ct.Render(queryIDOrName)
			if err != nil {
				return idx, nil, err
			}
			return idx, render, nil
		} else {
			return idx, wrapper.PreparedQuery, nil
		}
	}

	// Next, look for an exact name match. This is the common case for static
	// prepared queries, and could also apply to templates.
	{
		wrapped, err := tx.First("prepared-queries", "name", queryIDOrName)
		if err != nil {
			return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
		}
		if wrapped != nil {
			return prep(wrapped)
		}
	}

	// Next, look for the longest prefix match among the prepared query
	// templates.
	{
		wrapped, err := tx.LongestPrefix("prepared-queries", "template_prefix", queryIDOrName)
		if err != nil {
			return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
		}
		if wrapped != nil {
			return prep(wrapped)
		}
	}

	return idx, nil, nil
}