|_. Argument |_. Type |_. Description |_. Location |
|{resource_type}|object|Name is the singular form of the resource type, e.g., for the "collections" resource, this argument is "collection"|body|
|{cluster_id}|string|Optional, the cluster on which to create the object if not the current cluster.|query|
+|select |array |Attributes of the new object to return in the response (by default, all available attributes are returned).
+Example: @["uuid","name","modified_at"]@|query|
h2. delete
table(table table-bordered table-condensed).
|_. Argument |_. Type |_. Description |_. Location |
{background:#ccffcc}.|uuid|string|The UUID of the object in question.|path|
+|select |array |Attributes of the deleted object to return in the response (by default, all available attributes are returned).
+Example: @["uuid","name","modified_at"]@|query|
h2. get
table(table table-bordered table-condensed).
|_. Argument |_. Type |_. Description |_. Location |
{background:#ccffcc}.|uuid|string|The UUID of the object in question.|path|
+|select |array |Attributes of the object to return in the response (by default, all available attributes are returned).
+Example: @["uuid","name","modified_at"]@|query|
h2(#index). list
-The @list@ method requests an list of resources of that type. It corresponds to the HTTP request @GET /arvados/v1/resource_type@. All resources support "list" method unless otherwise noted.
+The @list@ method requests an list of resources of that type. It corresponds to the HTTP request @GET /arvados/v1/resource_type@. All resources support the @list@ method unless otherwise noted.
Arguments:
|order |array |Attributes to use as sort keys to determine the order resources are returned, each optionally followed by @asc@ or @desc@ to indicate ascending or descending order. (If not specified, it will be ascending).
Example: @["head_uuid asc","modified_at desc"]@
Default: @["modified_at desc", "uuid asc"]@|query|
-|select |array |Set of attributes to include in the response.
-Example: @["head_uuid","tail_uuid"]@
-Default: all available attributes. As a special case, collections do not return "manifest_text" unless explicitly selected.|query|
-|distinct|boolean|@true@: (default) do not return duplicate objects
-@false@: permitted to return duplicates|query|
+|select |array |Attributes of each object to return in the response (by default, all available attributes are returned, except collections, which do not return @manifest_text@ unless explicitly selected).
+Example: @["uuid","name","modified_at"]@|query|
+|distinct|boolean|When returning multiple records whose selected attributes (see @select@) are equal, return them as a single response entry.
+Default is @false@.|query|
|count|string|@"exact"@ (default): Include an @items_available@ response field giving the number of distinct matching items that can be retrieved (irrespective of @limit@ and @offset@ arguments).
@"none"@: Omit the @items_available@ response field. This option will produce a faster response.|query|
|_. Argument |_. Type |_. Description |_. Location |
{background:#ccffcc}.|uuid|string|The UUID of the resource in question.|path||
|{resource_type}|object||query||
+|select |array |Attributes of the updated object to return in the response (by default, all available attributes are returned).
+Example: @["uuid","name","modified_at"]@|query|
h3. get
-Gets a Collection's metadata by UUID or portable data hash. When making a request by portable data hash, the returned record will only have the @portable_data_hash@ and @manifest_text@.
+Gets a Collection's metadata by UUID or portable data hash. When making a request by portable data hash, attributes other than @portable_data_hash@ and @manifest_text@ are not returned, even when requested explicitly using the @select@ parameter.
Arguments:
table(table table-bordered table-condensed).
|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Collection in question.|path||
+{background:#ccffcc}.|uuid|string|The UUID or portable data hash of the Collection in question.|path||
h3. list
if err != nil {
return err
}
- // options.UUID is either hash+size or
- // hash+size+hints; only hash+size need to
- // match the computed PDH.
- if pdh := arvados.PortableDataHash(c.ManifestText); pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
- err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
- ctxlog.FromContext(ctx).Warn(err)
- return err
+ haveManifest := true
+ if options.Select != nil {
+ haveManifest = false
+ for _, s := range options.Select {
+ if s == "manifest_text" {
+ haveManifest = true
+ break
+ }
+ }
+ }
+ if haveManifest {
+ pdh := arvados.PortableDataHash(c.ManifestText)
+ // options.UUID is either hash+size or
+ // hash+size+hints; only hash+size need to
+ // match the computed PDH.
+ if pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
+ err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
+ ctxlog.FromContext(ctx).Warn(err)
+ return err
+ }
}
if remoteID != "" {
c.ManifestText = rewriteManifest(c.ManifestText, remoteID)
Filters: []arvados.Filter{{"uuid", "=", arvadostest.FooCollection}},
Select: []string{"uuid", "manifest_text"},
})
- c.Check(err, check.IsNil)
- if c.Check(gresp.Items, check.HasLen, 1) {
+ if err != nil {
+ c.Check(err, check.ErrorMatches, `.*Invalid attribute.*manifest_text.*`)
+ } else if c.Check(gresp.Items, check.HasLen, 1) {
c.Check(gresp.Items[0].(map[string]interface{})["uuid"], check.Equals, arvadostest.FooCollection)
c.Check(gresp.Items[0].(map[string]interface{})["manifest_text"], check.Equals, nil)
}
comment: "form-encoded expression filter in query string",
method: "GET",
path: "/arvados/v1/collections?filters=[%22(foo<bar)%22]",
- header: http.Header{"Content-Type": {"application/x-www-form-urlencoded"}},
shouldCall: "CollectionList",
withOptions: arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"(foo<bar)", "=", true}}},
},
shouldCall: "CollectionList",
withOptions: arvados.ListOptions{Limit: 2, Filters: []arvados.Filter{{"(foo<bar)", "=", true}, {"bar", "=", "baz"}}},
},
+ {
+ comment: "json-encoded select param in query string",
+ method: "GET",
+ path: "/arvados/v1/collections/" + arvadostest.FooCollection + "?select=[%22portable_data_hash%22]",
+ shouldCall: "CollectionGet",
+ withOptions: arvados.GetOptions{UUID: arvadostest.FooCollection, Select: []string{"portable_data_hash"}},
+ },
{
method: "PATCH",
path: "/arvados/v1/collections",
for _, sel := range [][]string{
{"uuid", "command"},
{"uuid", "command", "uuid"},
- {"", "command", "uuid"},
} {
j, err := json.Marshal(sel)
c.Assert(err, check.IsNil)
c.Check(rr.Code, check.Equals, http.StatusOK)
c.Check(resp["kind"], check.Equals, "arvados#container")
- c.Check(resp["etag"], check.FitsTypeOf, "")
- c.Check(resp["etag"], check.Not(check.Equals), "")
c.Check(resp["uuid"], check.HasLen, 27)
c.Check(resp["command"], check.HasLen, 2)
c.Check(resp["mounts"], check.IsNil)
before_action :catch_redirect_hint
before_action :load_required_parameters
+ before_action :load_limit_offset_order_params, only: [:index, :contents]
+ before_action :load_select_param
before_action(:find_object_by_uuid,
except: [:index, :create] + ERROR_ACTIONS)
- before_action(:set_nullable_attrs_to_null, only: [:update, :create])
- before_action :load_limit_offset_order_params, only: [:index, :contents]
before_action :load_where_param, only: [:index, :contents]
before_action :load_filters_param, only: [:index, :contents]
before_action :find_objects_for_index, :only => :index
+ before_action(:set_nullable_attrs_to_null, only: [:update, :create])
before_action :reload_object_before_update, :only => :update
before_action(:render_404_if_no_object,
except: [:index, :create] + ERROR_ACTIONS)
@objects = @objects.order(@orders.join ", ") if @orders.any?
@objects = @objects.limit(@limit)
@objects = @objects.offset(@offset)
- @objects = @objects.distinct(@distinct) if not @distinct.nil?
+ @objects = @objects.distinct() if @distinct
end
# limit_database_read ensures @objects (which must be an
def self._create_requires_parameters
{
+ select: {
+ type: 'array',
+ description: "Attributes of the new object to return in the response.",
+ required: false,
+ },
ensure_unique_name: {
type: "boolean",
description: "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
end
def self._update_requires_parameters
- {}
+ {
+ select: {
+ type: 'array',
+ description: "Attributes of the updated object to return in the response.",
+ required: false,
+ },
+ }
+ end
+
+ def self._show_requires_parameters
+ {
+ select: {
+ type: 'array',
+ description: "Attributes of the object to return in the response.",
+ required: false,
+ },
+ }
end
def self._index_requires_parameters
filters: { type: 'array', required: false },
where: { type: 'object', required: false },
order: { type: 'array', required: false },
- select: { type: 'array', required: false },
- distinct: { type: 'boolean', required: false },
+ select: {
+ type: 'array',
+ description: "Attributes of each object to return in the response.",
+ required: false,
+ },
+ distinct: { type: 'boolean', required: false, default: false },
limit: { type: 'integer', required: false, default: DEFAULT_LIMIT },
offset: { type: 'integer', required: false, default: 0 },
count: { type: 'string', required: false, default: 'exact' },
# it will select the Collection object with the longest
# available lifetime.
- if c = Collection.readable_by(*@read_users, opts).where({ portable_data_hash: loc.to_s }).order("trash_at desc").limit(1).first
+ select_attrs = (@select || ["manifest_text"]) | ["portable_data_hash", "trash_at"]
+ if c = Collection.
+ readable_by(*@read_users, opts).
+ where({ portable_data_hash: loc.to_s }).
+ order("trash_at desc").
+ select(select_attrs.join(", ")).
+ limit(1).
+ first
@object = {
uuid: c.portable_data_hash,
portable_data_hash: c.portable_data_hash,
- manifest_text: c.manifest_text,
+ trash_at: c.trash_at,
}
+ if select_attrs.index("manifest_text")
+ @object[:manifest_text] = c.manifest_text
+ end
end
else
super
protected
- def load_limit_offset_order_params *args
+ def load_select_param *args
super
if action_name == 'index'
# Omit manifest_text and unsigned_manifest_text from index results unless expressly selected.
skip_before_action :find_object_by_uuid
skip_before_action :load_filters_param
skip_before_action :load_limit_offset_order_params
+ skip_before_action :load_select_param
skip_before_action :load_read_auths
skip_before_action :load_where_param
skip_before_action :render_404_if_no_object
skip_before_action :find_object_by_uuid
skip_before_action :load_filters_param
skip_before_action :load_limit_offset_order_params
+ skip_before_action :load_select_param
skip_before_action :load_read_auths
skip_before_action :load_where_param
skip_before_action :render_404_if_no_object
end
end
+ @distinct = params[:distinct] && true
+ end
+
+ def load_select_param
case params[:select]
when Array
@select = params[:select]
end
end
- if @select
+ if @select && @orders
# Any ordering columns must be selected when doing select,
# otherwise it is an SQL error, so filter out invaliding orderings.
@orders.select! { |o|
@select.select { |s| col == "#{table_name}.#{s}" }.any?
}
end
-
- @distinct = true if (params[:distinct] == true || params[:distinct] == "true")
- @distinct = false if (params[:distinct] == false || params[:distinct] == "false")
end
end
end
end
end
+
+ test "select param is respected in 'show' response" do
+ authorize_with :active
+ get :show, params: {
+ id: collections(:collection_owned_by_active).uuid,
+ select: ["name"],
+ }
+ assert_response :success
+ assert_raises ActiveModel::MissingAttributeError do
+ assigns(:object).manifest_text
+ end
+ assert_nil json_response["manifest_text"]
+ assert_nil json_response["properties"]
+ assert_equal collections(:collection_owned_by_active).name, json_response["name"]
+ end
+
+ test "select param is respected in 'update' response" do
+ authorize_with :active
+ post :update, params: {
+ id: collections(:collection_owned_by_active).uuid,
+ collection: {
+ manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foobar.txt\n",
+ },
+ select: ["name"],
+ }
+ assert_response :success
+ assert_nil json_response["manifest_text"]
+ assert_nil json_response["properties"]
+ assert_equal collections(:collection_owned_by_active).name, json_response["name"]
+ end
+
+ [nil,
+ [],
+ ["is_trashed", "trash_at"],
+ ["is_trashed", "trash_at", "portable_data_hash"],
+ ["portable_data_hash"],
+ ["portable_data_hash", "manifest_text"],
+ ].each do |select|
+ test "select=#{select.inspect} param is respected in 'get by pdh' response" do
+ authorize_with :active
+ get :show, params: {
+ id: collections(:collection_owned_by_active).portable_data_hash,
+ select: select,
+ }
+ assert_response :success
+ if !select || select.index("manifest_text")
+ assert_not_nil json_response["manifest_text"]
+ else
+ assert_nil json_response["manifest_text"]
+ end
+ end
+ end
end
end
test "fewer distinct than total count" do
+ get "/arvados/v1/links",
+ params: {:format => :json, :select => ['link_class']},
+ headers: auth(:active)
+ assert_response :success
+ distinct_unspecified = json_response['items']
+
get "/arvados/v1/links",
params: {:format => :json, :select => ['link_class'], :distinct => false},
headers: auth(:active)
assert_response :success
- links = json_response['items']
+ distinct_false = json_response['items']
get "/arvados/v1/links",
params: {:format => :json, :select => ['link_class'], :distinct => true},
assert_response :success
distinct = json_response['items']
- assert_operator(distinct.count, :<, links.count,
- "distinct count should be less than link count")
- assert_equal links.uniq.count, distinct.count
+ assert_operator(distinct.count, :<, distinct_false.count,
+ "distinct=true count should be less than distinct=false count")
+ assert_equal(distinct_unspecified.count, distinct_false.count,
+ "distinct=false should be the default")
+ assert_equal distinct_false.uniq.count, distinct.count
end
test "select with order" do