HQL Multiple selects in the select clause - java

I would like to get in a single query the result of multiple counts. In a native query I can do that like:
`select (select count(*) from Foo where status = 1), (select count(*) from Foo where status = 2) ...`
If I do like that in HQL:
select (select count(f) from Foo f where f.status = 1), (select count(f) from Foo f where f.status = 2)
I get an error:
<AST>:0:0: unexpected end of subtree
How can I do that in HQL?

Your first select is incomplete if you complete it it should be fine.
select (select count(f) from Foo f where f.status = 1), (select count(f) from Foo f where f.status = 2) from Foo;

Related

Why is my native query faster than my JPA query?

I'm trying to understand why I have a native query that loads more data than a JPA one, yet the native query is much faster.
Here is my JPA query:
#NamedEntityGraph(
name = "ShipmentTbl.report.scheduling-delivery",
attributeNodes = {
#NamedAttributeNode("boxes"),
#NamedAttributeNode("pallets"),
#NamedAttributeNode("shipFrom"),
#NamedAttributeNode("shipmentReceive"),
#NamedAttributeNode("shipmentPurchaseOrders"),
#NamedAttributeNode("shipmentProducts"),
#NamedAttributeNode("distributionCenter"),
#NamedAttributeNode("scheduledDates")
}
)
.......
#EntityGraph(value = "ShipmentTbl.report.scheduling-delivery", type = EntityGraph.EntityGraphType.LOAD)
#Query("SELECT s FROM ShipmentTbl s " +
"JOIN s.shipmentReceive sr " +
"WHERE (sr.receivedDatetime BETWEEN :startDate AND :endDate) " +
"AND (:includeArchivedShipments = TRUE OR s.status <> 12)")
List<ShipmentTbl> getDeliveryReportData(#Param(value = "startDate") Timestamp startDate,
#Param(value = "endDate") Timestamp endDate,
#Param(value = "includeArchivedShipments") boolean includeArchivedShipments);
And here is my native query:
SELECT
s.id,
s.shipment_name,
max(dc.name) as dc,
max(sf.country) as country,
s.status,
s.estimated_shipment_date,
string_agg(CAST(ssd.scheduled_shipment_date as TEXT), ',') as scheduled_shipment_date,
string_agg(CAST(ssd.scheduled_period_of_day as TEXT), ',') as scheduled_period_of_day,
s.courier,
s.courier_amount,
s.courier_currency,
s.total_volume,
s.measurement_unit,
s.total_weight,
s.weight_unit,
s.tracking_number,
s.auth_code,
s.booked_in_by_user,
s.tenant,
(SELECT stm.date FROM status_transition_message stm WHERE (stm.initial_status = 0 AND stm.final_status = 1 OR stm.initial_status = 0 AND stm.final_status = 7) AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS submitted_dated,
(SELECT stm.user_full_name FROM status_transition_message stm WHERE (stm.initial_status = 0 AND stm.final_status = 1 OR stm.initial_status = 0 AND stm.final_status = 7) AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS submitted_by,
(SELECT stm.date FROM status_transition_message stm WHERE stm.initial_status = 1 AND stm.final_status = 3 AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS first_time_in_progress,
(SELECT stm.date FROM status_transition_message stm WHERE stm.initial_status = 3 AND stm.final_status = 4 AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS approved_date,
(SELECT stm.user_full_name FROM status_transition_message stm WHERE stm.initial_status = 3 AND stm.final_status = 4 AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS tt_user,
(SELECT stm.user_full_name FROM status_transition_message stm WHERE stm.initial_status = 4 AND (stm.final_status = 10 OR stm.final_status = 11) AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS received_by,
(SELECT string_agg(CAST(purchase_order_id AS TEXT), ',') FROM (SELECT spo.purchase_order_id FROM shipment_purchase_orders spo WHERE spo.shipment_id = s.id) AS purchase_orders) AS purchase_orders,
(SELECT string_agg(to_char(bookin_datetime, 'YYYY-MM-DD HH24:MI:SS.US'), ',') FROM (SELECT sr.bookin_datetime FROM shipment_receive sr WHERE sr.shipment_id = s.id) AS bd) AS bookin_datetimes,
(SELECT string_agg(to_char(received_datetime, 'YYYY-MM-DD HH24:MI:SS.US'), ',') FROM (SELECT sr.received_datetime FROM shipment_receive sr WHERE sr.shipment_id = s.id) AS dd) AS received_datetimes,
(SELECT string_agg(CAST(file_id AS TEXT), ',') FROM (SELECT spd.file_id FROM shipment_packing_documents spd WHERE spd.shipment_id = s.id) AS packing_docs) AS packing_doc_ids,
(SELECT string_agg(CAST(file_id AS TEXT), ',') FROM (SELECT std.file_id FROM shipment_tech_documents std WHERE std.shipment_id = s.id) AS tech_docs) AS tech_doc_ids,
(SELECT string_agg(CAST(number AS TEXT), ',') FROM (SELECT scd.number FROM shipment_customs_documents scd WHERE scd.shipment_id = s.id) AS customs_numbers) AS customs_numbers,
(SELECT string_agg(CAST(file_id AS TEXT), ',') FROM (SELECT file_id FROM shipment_invoices si WHERE si.shipment_id = s.id) AS invoice_docs) AS invoice_doc_ids,
(SELECT string_agg(CAST(number AS TEXT), ', ') FROM (SELECT si.number FROM shipment_invoices si WHERE si.shipment_id = s.id) AS invoice_numbers) AS invoice_numbers,
(SELECT sum(si.amount) FROM shipment_invoices si WHERE si.shipment_id = s.id) AS invoice_amount,
(SELECT sum(p.pallets_number) FROM pallets p WHERE p.shipment_id = s.id) AS pallets_number,
(SELECT sum(p.pallets_number * p.boxes_number) FROM pallets p WHERE p.shipment_id = s.id) AS boxes_by_pallet,
(SELECT sum(b.boxes_number) FROM boxes b WHERE b.shipment_id = s.id) AS boxes,
(SELECT string_agg(CAST(products AS TEXT), ',') FROM (SELECT sp.stock_order_id, sp.shipping_quantity FROM shipment_products sp WHERE sp.shipment_id = s.id) AS products) AS products,
(SELECT string_agg(DISTINCT CAST(status AS TEXT), ',') FROM products_status WHERE shipment_id = s.id) AS product_status,
s.tt_priority
(SELECT stm.date FROM status_transition_message stm WHERE (stm.initial_status = 0 AND stm.final_status = 1 OR stm.initial_status = 7 AND stm.final_status = 1) AND stm.shipment_id = s.id ORDER BY date ASC LIMIT 1) AS first_time_in_waiting_authorization,
" s.submitted_date as last_submitted_date
"FROM shipment_receive sr
JOIN shipment s ON sr.shipment_id = s.id
LEFT JOIN shipment_scheduled_date ssd on ssd.shipment_id = s.id
JOIN distribution_center dc ON s.ship_to = dc.id
JOIN address sf ON s.ship_from = sf.id
WHERE sr.received_datetime BETWEEN :startDate AND :endDate
AND (:includeArchivedShipments = TRUE OR s.status <> 12)
GROUP BY s.id";
I want to note these two queries are not equivalent, but they do load a lot of common fields and have the same conditions in the WHERE clauses.
I know the 2nd query is long and boring to look at, but the gist is, my entities are all lazy loaded. There's not a single eagerly loaded field. So, in the JPA query, the fields in the EntityGraph are the only fields eagerly loaded. Despite that, there are less joins happening in my first query (JPA) than in my second one, which joins the same tables as the first one and more. Yet, the second query takes an average of ~11 seconds to run whereas the first one takes ~20 seconds, both with the exact same parameters.
Can someone explain why? I know the first ends up loading more "normal" fields that are in the entities (like Strings, Dates, Integers...), but I'd still expect it to run faster.
(If it's relevant enough, I can also post the native SQL query Hibernate builds using the JPA query)
EDIT: This is the SQL query generated by Hibernate from the initial JPA query:
select
shipmenttb0_.id as id1_13_0_,
shipmentre1_.id as id1_22_1_,
boxes2_.id as id1_1_2_,
shipmentpu3_.id as id1_21_3_,
addresstbl4_.id as id1_0_4_,
scheduledd5_.id as id1_23_5_,
pallets6_.id as id1_7_6_,
shipmentpr7_.id as id1_20_7_,
distributi8_.id as id1_2_8_,
shipmenttb0_.archived_date as archived2_13_0_,
shipmenttb0_.auth_code as auth_cod3_13_0_,
shipmenttb0_.authorization_date as authoriz4_13_0_,
shipmenttb0_.booked_in_by_user as booked_i5_13_0_,
shipmenttb0_.business_channel as business6_13_0_,
shipmenttb0_.courier as courier7_13_0_,
shipmenttb0_.courier_amount as courier_8_13_0_,
shipmenttb0_.courier_currency as courier_9_13_0_,
shipmenttb0_.ship_to as ship_to40_13_0_,
shipmenttb0_.estimated_shipment_date as estimat10_13_0_,
shipmenttb0_.forecast_scheduled_date as forecas11_13_0_,
shipmenttb0_.last_updated_date as last_up12_13_0_,
shipmenttb0_.measurement_unit as measure13_13_0_,
shipmenttb0_.original_submitted_date as origina14_13_0_,
shipmenttb0_.packaging_type as packagi15_13_0_,
shipmenttb0_.placeholder_message as placeho16_13_0_,
shipmenttb0_.scheduled_period_of_day as schedul17_13_0_,
shipmenttb0_.scheduled_shipment_date as schedul18_13_0_,
shipmenttb0_.ship_from as ship_fr41_13_0_,
shipmenttb0_.ship_origin as ship_or42_13_0_,
shipmenttb0_.shipment_name as shipmen19_13_0_,
shipmenttb0_.status as status20_13_0_,
shipmenttb0_.submitted_date as submitt21_13_0_,
shipmenttb0_.supplier_contact_email as supplie22_13_0_,
shipmenttb0_.supplier_contact_name as supplie23_13_0_,
shipmenttb0_.supplier_contact_phone_number as supplie24_13_0_,
shipmenttb0_.supplier_email as supplie25_13_0_,
shipmenttb0_.supplier_secondary_contact_email as supplie26_13_0_,
shipmenttb0_.supplier_secondary_contact_name as supplie27_13_0_,
shipmenttb0_.supplier_secondary_contact_phone_number as supplie28_13_0_,
shipmenttb0_.tenant as tenant29_13_0_,
shipmenttb0_.total_received_boxes as total_r30_13_0_,
shipmenttb0_.total_units as total_u31_13_0_,
shipmenttb0_.total_value as total_v32_13_0_,
shipmenttb0_.total_volume as total_v33_13_0_,
shipmenttb0_.total_weight as total_w34_13_0_,
shipmenttb0_.tracking_number as trackin35_13_0_,
shipmenttb0_.tt_note as tt_note36_13_0_,
shipmenttb0_.tt_priority as tt_prio37_13_0_,
shipmenttb0_.updated_by_user as updated38_13_0_,
shipmenttb0_.weight_unit as weight_39_13_0_,
shipmentre1_.bookin_datetime as bookin_d2_22_1_,
shipmentre1_.received_type as received3_22_1_,
shipmentre1_.delay_days as delay_da4_22_1_,
shipmentre1_.received_boxes as received5_22_1_,
shipmentre1_.received_datetime as received6_22_1_,
shipmentre1_.shipment_scheduled_date as shipment9_22_1_,
shipmentre1_.session_active as session_7_22_1_,
shipmentre1_.shipment_id as shipmen10_22_1_,
shipmentre1_.status as status8_22_1_,
shipmentre1_.shipment_id as shipmen10_22_0__,
shipmentre1_.id as id1_22_0__,
boxes2_.added_time as added_ti2_1_2_,
boxes2_.boxes_number as boxes_nu3_1_2_,
boxes2_.height as height4_1_2_,
boxes2_.length as length5_1_2_,
boxes2_.shipment_id as shipment7_1_2_,
boxes2_.width as width6_1_2_,
boxes2_.shipment_id as shipment7_1_1__,
boxes2_.id as id1_1_1__,
shipmentpu3_.purchase_order_id as purchase2_21_3_,
shipmentpu3_.shipment_id as shipment3_21_3_,
shipmentpu3_.shipment_id as shipment3_21_2__,
shipmentpu3_.id as id1_21_2__,
addresstbl4_.address_line1 as address_2_0_4_,
addresstbl4_.address_line2 as address_3_0_4_,
addresstbl4_.address_line3 as address_4_0_4_,
addresstbl4_.address_line4 as address_5_0_4_,
addresstbl4_.city as city6_0_4_,
addresstbl4_.company_name as company_7_0_4_,
addresstbl4_.contact_email as contact_8_0_4_,
addresstbl4_.contact_name as contact_9_0_4_,
addresstbl4_.contact_phone as contact10_0_4_,
addresstbl4_.country as country11_0_4_,
addresstbl4_.postal_code as postal_12_0_4_,
addresstbl4_.time_zone_id as time_zo13_0_4_,
addresstbl4_.title as title14_0_4_,
scheduledd5_.estimated_product_item_units as estimate2_23_5_,
scheduledd5_.expected_boxes as expected3_23_5_,
scheduledd5_.expected_volume as expected4_23_5_,
scheduledd5_.scheduled_period_of_day as schedule5_23_5_,
scheduledd5_.scheduled_shipment_date as schedule6_23_5_,
scheduledd5_.shipment_id as shipment7_23_5_,
scheduledd5_.shipment_id as shipment7_23_3__,
scheduledd5_.id as id1_23_3__,
pallets6_.added_time as added_ti2_7_6_,
pallets6_.boxes_number as boxes_nu3_7_6_,
pallets6_.height as height4_7_6_,
pallets6_.length as length5_7_6_,
pallets6_.pallets_number as pallets_6_7_6_,
pallets6_.shipment_id as shipment8_7_6_,
pallets6_.width as width7_7_6_,
pallets6_.shipment_id as shipment8_7_4__,
pallets6_.id as id1_7_4__,
shipmentpr7_.country_of_origin as country_2_20_7_,
shipmentpr7_.hs_code as hs_code3_20_7_,
shipmentpr7_.is_new as is_new4_20_7_,
shipmentpr7_.manufacturer_id as manufact5_20_7_,
shipmentpr7_.note as note6_20_7_,
shipmentpr7_.preferential_origin as preferen7_20_7_,
shipmentpr7_.shipment_id as shipmen10_20_7_,
shipmentpr7_.shipping_quantity as shipping8_20_7_,
shipmentpr7_.stock_order_id as stock_or9_20_7_,
(select
1) as formula0_7_,
shipmentpr7_.shipment_id as shipmen10_20_5__,
shipmentpr7_.id as id1_20_5__,
distributi8_.address_id as address_9_2_8_,
distributi8_.label as label2_2_8_,
distributi8_.manufacturer_id_required as manufact3_2_8_,
distributi8_.max_product_items_units as max_prod4_2_8_,
distributi8_.max_products_units as max_prod5_2_8_,
distributi8_.max_volume as max_volu6_2_8_,
distributi8_.name as name7_2_8_,
distributi8_.volume_unit as volume_u8_2_8_
from
shipment shipmenttb0_
inner join
shipment_receive shipmentre1_
on shipmenttb0_.id=shipmentre1_.shipment_id
left outer join
boxes boxes2_
on shipmenttb0_.id=boxes2_.shipment_id
left outer join
shipment_purchase_orders shipmentpu3_
on shipmenttb0_.id=shipmentpu3_.shipment_id
left outer join
address addresstbl4_
on shipmenttb0_.ship_from=addresstbl4_.id
left outer join
shipment_scheduled_date scheduledd5_
on shipmenttb0_.id=scheduledd5_.shipment_id
left outer join
pallets pallets6_
on shipmenttb0_.id=pallets6_.shipment_id
left outer join
shipment_products shipmentpr7_
on shipmenttb0_.id=shipmentpr7_.shipment_id
left outer join
distribution_center distributi8_
on shipmenttb0_.ship_to=distributi8_.id
where
(
shipmentre1_.received_datetime between ? and ?
)
and (
?=true
or shipmenttb0_.status<>12
)
Like I said, it does a couple of joins, but nothing more than the other native query, so it should be faster.
Most likely your JPA query is not running a single query like you are expecting. I know you said you think you have everything LAZY loaded, but you really need to turn on SQL logging to figure this out.
Whatever your logging platform is, change this logger to DEBUG
org.hibernate.SQL: DEBUG

unexpected end of subtree JPQL

I have a query which count a given code from 4 tables. I tested this query first in postgresql, it worked as I expected so I tried to translate it to JPQL and I got this error :
java.lang.IllegalArgumentException:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected end of
subtree [ select ((select count(*) from *.Record r where
r.codeCampaign =?1 ) + (select count(*) from *.AccountAssociationAction aaa where aaa.codeCampaign = ?1) +
(select count(*) from *.CampaignPayment cp where cp.pk.campaignCode = ?1) + (select count(*) from
*.ActionPeriodDepartment apd
where apd.id.codeCampaign = ?1))]
I can't figure out what is wrong and what does Hibernate mean by "unexpected end of subtree"
The postgresql query :
select (select count(*) from account_association_action aaa where
aaa.code_campaign = 'CAMP01') + (select count(*) from _campaign_payment cp
where cp.campaign_code = 'CAMP01') + (select count(*) from record r where
r.code_campaign ='CAMP01') + (select count(*) from action_period_department apd
where apd.code_campaign = 'CAMP01');
the JPQL query :
#Query(value=" select (select count(*) from Record r where r.codeCampaign =?1 ) + " +
" (select count(*) from AccountAssociationAction aaa where aaa.codeCampaign = ?1) +" +
" (select count(*) from CampaignPayment cp where cp.pk.campaignCode = ?1) +" +
" (select count(*) from ActionPeriodDepartment apd where apd.id.codeCampaign = ?1)")
int countCampaignCodeUses(String campaignCode);
It looks like you need to add nativeQuery=true to #Query annotation otherwise JPA is failing to make sense of a query without from

named query to fetch result by using IN clause with null or without null conditionally

I need a single named query that fulfills below both named query conditions only by setting the Query parameter.
Named Query, to fetch record where "softwareVersion" is null OR softwareVersion matched with the list.
#NamedQuery(name = "getIPDetectionDetailsForPanIndia",
query = "select cssc.css.sapId,
cssc.css.hostName,
cssc.cssGoldenConfiguration.category,
cssc.cssGoldenConfiguration.parameter,
cssc.cssGoldenConfiguration.vendor,
cssc.recommendedValue,
cssc.actualValue,
cssc.cssGoldenConfiguration.id,
cssc.cssGoldenConfiguration.information,
cssc.css.softwareVersion,
jioc.name,
cssc.cssGoldenConfiguration.impact,
cssc.cssGoldenConfiguration.command
from CssComplianceDetail cssc
join cssc.css c
left join c.cluster club
left join clus.jiocenter jioc
where cssc.cssGoldenConfiguration.impact in (:impactType)
and cssc.cssGoldenConfiguration.category in (:category)
and TO_CHAR(cssc.creationDate, 'yyyy-MM-dd') = TO_CHAR(:date, 'yyyy-MM-dd')
and (cssc.css.softwareVersion in (:softwareVersion)
or cssc.css.softwareVersion is null)";
Named Query, to fatch record where "softwareVersion" matched with the list only, no need to fatch null softwareVersion.
#NamedQuery(name = "getIPDetectionDetailsForPanIndiaAcceptNull",
query = "select cssc.css.sapId,
cssc.css.hostName,
cssc.cssGoldenConfiguration.category,
cssc.cssGoldenConfiguration.parameter,
cssc.cssGoldenConfiguration.vendor,
cssc.recommendedValue,
cssc.actualValue,
cssc.cssGoldenConfiguration.id,
cssc.cssGoldenConfiguration.information,
cssc.css.softwareVersion,
jioc.name,
cssc.cssGoldenConfiguration.impact,
cssc.cssGoldenConfiguration.command
from CssComplianceDetail cssc
join cssc.css c
left join c.cluster club
left join clus.jiocenter join
where cssc.cssGoldenConfiguration.impact in (:impactType)
and cssc.cssGoldenConfiguration.category in (:category)
and TO_CHAR(cssc.creationDate, 'yyyy-MM-dd') = TO_CHAR(:date, 'yyyy-MM-dd')
and cssc.css.softwareVersion in (:softwareVersion)";
Below is my Java code:
List softwareVersion = new ArrayList();
if(softwareVersion.contains("All")) {
query = getEntityManager().createNamedQuery("getIPDetectionCountForPanIndiaAcceptNull");
} else {
query = getEntityManager().createNamedQuery("getIPDetectionCountForPanIndia");
}
query.setParameter("softwareVersion", softwareVersion);

HSQLDB HAVING object not found

Hi there I am having trouble bringing a mysql query in hsqldb to work for a unit test.
Unfort I can't show you the real code.
I tried to make it as simple as possible:
SELECT A.id,
(SELECT count(*) FROM SomeOtherTable) as myAlias
FROM Orders A WHERE A.someKz = 2 HAVING 1 = myAlias;
Will result in
user lacks privilege or object not found: MYALIAS
So my question is, how to access the variable myAlias in the HAVING clause?
This query might not look yousefull to you but it is very simplified. For Example there are serveral complex Subselects with variables.
UPDATE
This one has basically the same problem
SELECT A.id, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.refNR > 0) as anz1, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id ) as anz2, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.SID = 18 ) as anz3 FROM ABable A WHERE
A.someInt IN(1,2)
AND A.someString > '20150308190127'
AND(SELECT Count(*) FROM DTable D WHERE D.Aid = A.id ) = 0 HAVING anz1 = anz2 AND anz3 < anz2 ORDER BY someString ASC LIMIT 1;
Your example can be written as this for HSQLDB. It returns a result only if the Orders table contains a single row. But if the 1 is actually another expression in the SELECT list, you need to write it out:
SELECT A.id, 1 as myAlias
FROM Orders A WHERE A.someKz = 2 AND (SELECT count(*) FROM Orders) = 1
SELECT A.id, A.acol * 12 as myAlias
FROM Orders A WHERE A.someKz = 2 AND (SELECT count(*) FROM Orders) = A.acol *12
Your full query is far more complex. You should therefore write it with a wrapper SELECT statement:
SELECT * FROM (
SELECT A.id, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.refNR > 0) as anz1, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id ) as anz2, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.SID = 18 ) as anz3 FROM ABable A
WHERE A.someInt IN(1,2)
AND A.someString > '20150308190127'
AND(SELECT Count(*) FROM DTable D WHERE D.Aid = A.id ) = 0
) WHERE anz1 = anz2 AND anz3 < anz2 ORDER BY someString ASC LIMIT 1;

complex query to equivalent criteriabuilder query(EntityManager)

My Query is this:
query1 = select a.id from entity1 a where a.id in (:List1)
and not exists (select ex2 from entity2 ex2 where ex2.assignedId = a.id)
union
select ex.assignedId from entity2 ex ,entity3 pi
where ex.entity3Id = pi.id and ex.assignedId in (:List1)
and ex.assignedTypeId = :assignedTypeId and pi.processStatus = :status
and not exists
(select ex1.assignedId from entity2 ex1 , entity3 pi1
where ex1.entity3Id = pi1.id and ex1.assignedId = ex.assignedId
and ex1.assignedTypeId = :assignedTypeId
and pi1.processStatus <> :status);
and while trying to execute query,
Query existingIds=em.createQuery(query1); //With all parameters set
throws NullPointerException in line 87 of org.hibernate.hql.ast.ParameterTranslationsImpl
completely checked all the braces and parameters. The equivalent conversion works in mysql.
Can someone assist me in converting the query with CriteriaBuilder, finding it difficult to make the conversion.
Not sure if JPQL supports union operation at all. Are you putting this as NamedQuery or you are creating on the fly (entityManager.createQuery()) ?

Categories

Resources